RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 694846
Accepted
Qwertiy
Qwertiy
Asked:2020-07-21 05:41:27 +0000 UTC2020-07-21 05:41:27 +0000 UTC 2020-07-21 05:41:27 +0000 UTC

字符串和字符串生成器

  • 772

在 Java 和 C# 等语言中,通常使用 StringBuilder 连接大量字符串以获得线性渐近而不是二次渐近。

然而,JavaScript 以某种方式只能处理一种 String 类型。级联的渐近线在那里是线性的,至少对于高达 131072 次迭代的循环。但是,奇怪的是,时间并不取决于添加的字符串的长度。至少在 Chrome 中是这样的。

这是怎么发生的?

还有一个给 JS 鉴赏家的额外问题:262144 到底发生了什么?

http://ideone.com/gtm5iy

using System;
using System.Collections.Generic;
using System.Diagnostics;

public class Program
{
  private static string Test(int n, string s)
  {
    var res = "";

    for (var q=0; q<n; ++q)
      res += s;

    return res;
  }

  public static void Main()
  {
    var res = new Dictionary<string, double>[10];
    const int N = 1024;
    var sw = new Stopwatch();

    for (var n=0; n<res.Length; ++n)
    {
      res[n] = new Dictionary<string, double>();

      foreach (var s in new string[] {"!", "!2", "!234", "!2345678"})
      {
        res[n][s] = 0;

        for (var q=0; q<N; ++q)
        {
          sw.Restart();
          Test(1 << n, s);
          sw.Stop();
          res[n][s] += sw.ElapsedTicks;
        }

        res[n][s] /= N;
      }
    }

    for (var n=0; n<res.Length; ++n)
    {
      Console.Write("{0,2} {1,7} ", n, 1<<n);

      foreach (var kvp in res[n])
        Console.Write("{0,10:0.000} ", kvp.Value / 1000);

      Console.WriteLine();
    }
  }
}
 0       1      0.001      0.000      0.000      0.000 
 1       2      0.002      0.001      0.001      0.001 
 2       4      0.003      0.003      0.005      0.003 
 3       8      0.006      0.006      0.007      0.007 
 4      16      0.013      0.015      0.013      0.014 
 5      32      0.025      0.025      0.032      0.034 
 6      64      0.050      0.059      0.070      0.097 
 7     128      0.120      0.142      0.220      0.303 
 8     256      0.337      0.417      0.630      0.972 
 9     512      0.897      1.256      1.964      4.087

不幸的是,随着迭代次数的增加,该程序不适合在 ideone 上分配的 5 秒执行时间。以下是我家用电脑的结果:

D:\Temp\Supertemp>C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe StringConcat.cs && StringConcat.exe
Microsoft (R) Visual C# Compiler version 4.7.2046.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240

 0       1      0.000      0.000      0.000      0.000
 1       2      0.000      0.000      0.000      0.000
 2       4      0.000      0.000      0.000      0.000
 3       8      0.001      0.001      0.001      0.001
 4      16      0.002      0.001      0.001      0.002
 5      32      0.002      0.004      0.004      0.004
 6      64      0.005      0.006      0.009      0.014
 7     128      0.013      0.019      0.028      0.043
 8     256      0.042      0.057      0.087      0.148
 9     512      0.118      0.174      0.302      0.559
10    1024      0.354      0.606      1.124      2.279
11    2048      1.220      2.242      4.545     10.041
12    4096      4.517      8.982     19.706     41.568
13    8192     17.864     39.063     82.814    169.274
14   16384     78.454    165.893    337.830    718.843

function test(n, s) {
  var res = '';

  for (var q=0; q<n; ++q) {
    res += s;
  }

  return res;
}

var res = [], N = 1024;

for (var n=0; n<=18; ++n) {
  res.push({len: 1<<n});

  for (var s of ['!', '!2', '!234', '!2345678']) {
    res[n][s] = 0;

    for (var q=0; q<N; ++q) {
      var t = performance.now();
      test(1 << n, s);
      t = performance.now() - t;
      res[n][s] += t;
    }

    res[n][s] /= N;
    res[n][s] = res[n][s].toFixed(3);
  }
}

console.table(res)

截图

javascript
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Kyubey
    2020-07-21T22:12:56Z2020-07-21T22:12:56Z

    为什么会这样?

    从V8 sorts来看,字符串针对连接进行了优化。连接时,不是创建新字符串,而是创建一个引用被连接片段的实例。每个块也可以引用子块。这样一棵二叉树就建立起来了。

    // The ConsString class describes string values built by using the
    // addition operator on strings.  A ConsString is a pair where the
    // first and second components are pointers to other string values.
    // One or both components of a ConsString can be pointers to other
    // ConsStrings, creating a binary tree of ConsStrings where the leaves
    // are non-ConsString string values.  The string value represented by
    // a ConsString can be obtained by concatenating the leaf string
    // values in a left-to-right depth-first traversal of the tree.
    class ConsString : public String {
     public:
      // First string of the cons cell.
      inline String* first();
      inline void set_first(String* first,
        WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
      // Second string of the cons cell.
      inline String* second();
      inline void set_second(String* second,
        WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
      // Minimum length for a cons string.
      static const int kMinLength = 13;
      // ...
    }
    

    还有子字符串的字符串子类,单字节和双字节字符串显式分开等等。

    // The Sliced String class describes strings that are substrings of another
    // sequential string.  The motivation is to save time and memory when creating
    // a substring.  A Sliced String is described as a pointer to the parent,
    // the offset from the start of the parent string and the length.  Using
    // a Sliced String therefore requires unpacking of the parent string and
    // adding the offset to the start address.  A substring of a Sliced String
    // are not nested since the double indirection is simplified when creating
    // such a substring.
    // Currently missing features are:
    //  - handling externalized parent strings
    //  - external strings as parent
    //  - truncating sliced string to enable otherwise unneeded parent to be GC'ed.
    class SlicedString : public String {
     public:
      inline String* parent();
      inline void set_parent(String* parent,
                             WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
      inline int offset() const;
      inline void set_offset(int offset);
      // Minimum length for a sliced string.
      static const int kMinLength = 13;
      // ...
    }
    

    结果,像连接这样简单的操作变成了这样:

    MaybeHandle<String> Factory::NewConsString(Handle<String> left,
                                               Handle<String> right) {
      if (left->IsThinString()) {
        left = handle(Handle<ThinString>::cast(left)->actual(), isolate());
      }
      if (right->IsThinString()) {
        right = handle(Handle<ThinString>::cast(right)->actual(), isolate());
      }
      int left_length = left->length();
      if (left_length == 0) return right;
      int right_length = right->length();
      if (right_length == 0) return left;
    
      int length = left_length + right_length;
    
      if (length == 2) {
        uint16_t c1 = left->Get(0);
        uint16_t c2 = right->Get(0);
        return MakeOrFindTwoCharacterString(isolate(), c1, c2);
      }
    
      // Make sure that an out of memory exception is thrown if the length
      // of the new cons string is too large.
      if (length > String::kMaxLength || length < 0) {
        THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), String);
      }
    
      bool left_is_one_byte = left->IsOneByteRepresentation();
      bool right_is_one_byte = right->IsOneByteRepresentation();
      bool is_one_byte = left_is_one_byte && right_is_one_byte;
      bool is_one_byte_data_in_two_byte_string = false;
      if (!is_one_byte) {
        // At least one of the strings uses two-byte representation so we
        // can't use the fast case code for short one-byte strings below, but
        // we can try to save memory if all chars actually fit in one-byte.
        is_one_byte_data_in_two_byte_string =
            left->HasOnlyOneByteChars() && right->HasOnlyOneByteChars();
        if (is_one_byte_data_in_two_byte_string) {
          isolate()->counters()->string_add_runtime_ext_to_one_byte()->Increment();
        }
      }
    
      // If the resulting string is small make a flat string.
      if (length < ConsString::kMinLength) {
        // Note that neither of the two inputs can be a slice because:
        STATIC_ASSERT(ConsString::kMinLength <= SlicedString::kMinLength);
        DCHECK(left->IsFlat());
        DCHECK(right->IsFlat());
    
        STATIC_ASSERT(ConsString::kMinLength <= String::kMaxLength);
        if (is_one_byte) {
          Handle<SeqOneByteString> result =
              NewRawOneByteString(length).ToHandleChecked();
          DisallowHeapAllocation no_gc;
          uint8_t* dest = result->GetChars();
          // Copy left part.
          const uint8_t* src =
              left->IsExternalString()
                  ? Handle<ExternalOneByteString>::cast(left)->GetChars()
                  : Handle<SeqOneByteString>::cast(left)->GetChars();
          for (int i = 0; i < left_length; i++) *dest++ = src[i];
          // Copy right part.
          src = right->IsExternalString()
                    ? Handle<ExternalOneByteString>::cast(right)->GetChars()
                    : Handle<SeqOneByteString>::cast(right)->GetChars();
          for (int i = 0; i < right_length; i++) *dest++ = src[i];
          return result;
        }
    
        return (is_one_byte_data_in_two_byte_string)
            ? ConcatStringContent<uint8_t>(
                NewRawOneByteString(length).ToHandleChecked(), left, right)
            : ConcatStringContent<uc16>(
                NewRawTwoByteString(length).ToHandleChecked(), left, right);
      }
    
      bool one_byte = (is_one_byte || is_one_byte_data_in_two_byte_string);
      return NewConsString(left, right, length, one_byte);
    }
    

    任何这样的智慧总是一种妥协,上面覆盖着启发式方法。JavaScript 引擎会尝试猜测何时使用哪种方法更有利可图:何时创建完整的字符串,何时构建字符串的二叉树;当连接速度更重要时,当迭代速度更重要时;等等。

    一个明显的优势:可能非常慢的操作变得更快。这对于程序员经常使用某些类使用模式的情况尤其重要,例如字符串连接。但缺点也很明显:其他操作变慢,此外,引擎并不总是猜测程序员的意图并选择最优化的方式。

    为什么这是在 JavaScript 中完成的?

    因为有意使语言易于理解和使用。魔法越多,你就越不用考虑内部表示,写程序就越容易。

    此外,在创建该语言时,执行速度根本不重要,因为没有人用该语言编写复杂的程序。复杂的程序出现得晚得多。如果您不是在最新版本的 Chrome 中运行此程序,而是在任何 20 年前的浏览器中运行,您会发现性能有很大不同。

    为什么不用其他语言来完成?

    这取决于语言及其目的。例如,C++中的字符串简单而笨拙,要求程序员指定每个动作,在循环体中或外部声明字符串,通过引用和通过值将字符串传递给函数之间在性能上存在差异,程序员需要提前指明字符串的长度,等等。进一步。

    另一方面,在 PHP 中,数组就像 JS 中的字符串,在复杂性方面会让你领先一步。数组可以是向量、字典、集合等。它们具有针对每种情况进行优化的复杂结构。

    通常,标准类的方法因语言而异,甚至在同一语言编译器的不同版本之间也不同。今天,类型化数组出现在 JavaScript 和 PHP 中,明天可能会出现 StringBuilder。

    • 21

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5