| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7433c3d commit 5b3bb28
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,53 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common.js'); | ||
| 4 | + | ||
| 5 | + const bench = common.createBenchmark(main, { | ||
| 6 | + size: [64, 1024, 16384, 262144, 4194304], | ||
| 7 | + content: ['ascii', 'latin1', 'utf8_mixed', 'latin1_then_cjk'], | ||
| 8 | + n: [1e4], | ||
| 9 | + }); | ||
| 10 | + | ||
| 11 | + function buildBuffer(kind, size) { | ||
| 12 | + if (kind === 'ascii') { | ||
| 13 | + return Buffer.alloc(size, 0x61); | ||
| 14 | + } | ||
| 15 | + if (kind === 'latin1') { | ||
| 16 | + const pair = Buffer.from([0xC3, 0xA9]); | ||
| 17 | + const buf = Buffer.alloc(size); | ||
| 18 | + for (let i = 0; i + 2 <= size; i += 2) pair.copy(buf, i); | ||
| 19 | + return buf; | ||
| 20 | + } | ||
| 21 | + if (kind === 'utf8_mixed') { | ||
| 22 | + const cjk = Buffer.from([0xE4, 0xB8, 0xAD]); | ||
| 23 | + const buf = Buffer.alloc(size); | ||
| 24 | + let i = 0; | ||
| 25 | + while (i + 4 <= size) { | ||
| 26 | + buf[i++] = 0x61; | ||
| 27 | + cjk.copy(buf, i); | ||
| 28 | + i += 3; | ||
| 29 | + } | ||
| 30 | + return buf; | ||
| 31 | + } | ||
| 32 | + if (kind === 'latin1_then_cjk') { | ||
| 33 | + const pair = Buffer.from([0xC3, 0xA9]); | ||
| 34 | + const cjk = Buffer.from([0xE4, 0xB8, 0xAD]); | ||
| 35 | + const buf = Buffer.alloc(size); | ||
| 36 | + const mid = (size >> 1) & ~1; | ||
| 37 | + for (let i = 0; i + 2 <= mid; i += 2) pair.copy(buf, i); | ||
| 38 | + cjk.copy(buf, mid); | ||
| 39 | + for (let i = mid + 3; i + 2 <= size; i += 2) pair.copy(buf, i); | ||
| 40 | + return buf; | ||
| 41 | + } | ||
| 42 | + throw new Error('unknown content: ' + kind); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + function main({ n, size, content }) { | ||
| 46 | + const buf = buildBuffer(content, size); | ||
| 47 | + | ||
| 48 | + bench.start(); | ||
| 49 | + for (let i = 0; i < n; i++) { | ||
| 50 | + buf.toString('utf8'); | ||
| 51 | + } | ||
| 52 | + bench.end(n); | ||
| 53 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -582,21 +582,86 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate, | |||
| 582 | 582 | return ExternOneByteString::NewFromCopy(isolate, buf, buflen); | |
| 583 | 583 | } | |
| 584 | 584 | ||
| 585 | - if (buflen >= 32 && simdutf::validate_utf8(buf, buflen)) { | ||
| 586 | - // We know that we are non-ASCII (and are unlikely Latin1), use 2-byte | ||
| 587 | - // In the most likely case of valid UTF-8, we can use this fast impl | ||
| 588 | - // For very short input, it is slower, so we limit min size | ||
| 589 | - size_t u16size = simdutf::utf16_length_from_utf8(buf, buflen); | ||
| 590 | - if (u16size > static_cast<size_t>(v8::String::kMaxLength)) { | ||
| 591 | - isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); | ||
| 592 | - return MaybeLocal<Value>(); | ||
| 585 | + // Latin1-fits fast path: one-byte V8 string, half the heap of UTF-16. | ||
| 586 | + // Capped at 1 MiB (above that the prescan cost erases the win). | ||
| 587 | + constexpr size_t kLatin1Max = 1u << 20; | ||
| 588 | + if (buflen >= 256 && buflen <= kLatin1Max) { | ||
| 589 | + // Skip the allocation when any byte >= 0xC4 (UTF-8 lead for a | ||
| 590 | + // codepoint > U+FF). Inner loop has no early exit so clang | ||
| 591 | + // vectorizes it. | ||
| 592 | + constexpr size_t kChunk = 64; | ||
| 593 | + bool maybe_latin1 = true; | ||
| 594 | + size_t i = 0; | ||
| 595 | + for (; i + kChunk <= buflen; i += kChunk) { | ||
| 596 | + uint8_t acc = 0; | ||
| 597 | + for (size_t j = 0; j < kChunk; j++) { | ||
| 598 | + acc |= static_cast<uint8_t>(buf[i + j]) >= 0xC4 ? 1 : 0; | ||
| 599 | + } | ||
| 600 | + if (acc) { | ||
| 601 | + maybe_latin1 = false; | ||
| 602 | + break; | ||
| 603 | + } | ||
| 604 | + } | ||
| 605 | + if (maybe_latin1) { | ||
| 606 | + for (; i < buflen; i++) { | ||
| 607 | + if (static_cast<uint8_t>(buf[i]) >= 0xC4) { | ||
| 608 | + maybe_latin1 = false; | ||
| 609 | + break; | ||
| 610 | + } | ||
| 611 | + } | ||
| 612 | + } | ||
| 613 | + if (maybe_latin1) { | ||
| 614 | + MaybeStackBuffer<char, 4096> latin1; | ||
| 615 | + latin1.AllocateSufficientStorage(buflen); | ||
| 616 | + simdutf::result l1 = simdutf::convert_utf8_to_latin1_with_errors( | ||
| 617 | + buf, buflen, latin1.out()); | ||
| 618 | + if (l1.error == simdutf::error_code::SUCCESS) { | ||
| 619 | + return ExternOneByteString::NewFromCopy( | ||
| 620 | + isolate, latin1.out(), l1.count); | ||
| 621 | + } | ||
| 622 | + } | ||
| 623 | + } | ||
| 624 | + | ||
| 625 | + if (buflen >= 32) { | ||
| 626 | + // Single-pass UTF-16: over-allocate (1 char16_t per byte), then | ||
| 627 | + // shrink. Above 1 MiB the exact-size 3-pass below is cheaper. | ||
| 628 | + constexpr size_t kSinglePassMax = 1u << 20; | ||
| 629 | + if (buflen <= kSinglePassMax) { | ||
| 630 | + MaybeStackBuffer<uint16_t, 256> u16; | ||
| 631 | + u16.AllocateSufficientStorage(buflen); | ||
| 632 | + simdutf::result r = simdutf::convert_utf8_to_utf16_with_errors( | ||
| 633 | + buf, buflen, reinterpret_cast<char16_t*>(u16.out())); | ||
| 634 | + if (r.error == simdutf::error_code::SUCCESS) { | ||
| 635 | + if (r.count > static_cast<size_t>(v8::String::kMaxLength)) { | ||
| 636 | + isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); | ||
| 637 | + return MaybeLocal<Value>(); | ||
| 638 | + } | ||
| 639 | + if (u16.IsAllocated()) { | ||
| 640 | + uint16_t* data = u16.out(); | ||
| 641 | + u16.Release(); | ||
| 642 | + uint16_t* shrunk = static_cast<uint16_t*>( | ||
| 643 | + realloc(data, r.count * sizeof(uint16_t))); | ||
| 644 | + if (shrunk == nullptr) shrunk = data; | ||
| 645 | + return ExternTwoByteString::New(isolate, shrunk, r.count); | ||
| 646 | + } | ||
| 647 | + return String::NewFromTwoByte(isolate, | ||
| 648 | + u16.out(), | ||
| 649 | + v8::NewStringType::kNormal, | ||
| 650 | + static_cast<int>(r.count)); | ||
| 651 | + } | ||
| 652 | + } else if (simdutf::validate_utf8(buf, buflen)) { | ||
| 653 | + size_t u16size = simdutf::utf16_length_from_utf8(buf, buflen); | ||
| 654 | + if (u16size > static_cast<size_t>(v8::String::kMaxLength)) { | ||
| 655 | + isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); | ||
| 656 | + return MaybeLocal<Value>(); | ||
| 657 | + } | ||
| 658 | + return EncodeTwoByteString( | ||
| 659 | + isolate, u16size, [buf, buflen, u16size](uint16_t* dst) { | ||
| 660 | + size_t written = simdutf::convert_valid_utf8_to_utf16( | ||
| 661 | + buf, buflen, reinterpret_cast<char16_t*>(dst)); | ||
| 662 | + CHECK_EQ(written, u16size); | ||
| 663 | + }); | ||
| 593 | 664 | } | |
| 594 | - return EncodeTwoByteString( | ||
| 595 | - isolate, u16size, [buf, buflen, u16size](uint16_t* dst) { | ||
| 596 | - size_t written = simdutf::convert_valid_utf8_to_utf16( | ||
| 597 | - buf, buflen, reinterpret_cast<char16_t*>(dst)); | ||
| 598 | - CHECK_EQ(written, u16size); | ||
| 599 | - }); | ||
| 600 | 665 | } | |
| 601 | 666 | ||
| 602 | 667 | val = | |
| Back | FazBrowse Home | New Git URL |
0 commit comments