资讯专栏INFORMATION COLUMN

String.prototype.repeat在V8和Chakra中的实现

wushuiyong / 2223人阅读

摘要:作者源地址最近一个事件搞得圈沸沸扬扬的我们暂且把这个事情放一边来看看本身的实现的源码如下这段程序的作用是给一个字符串或可以转成的变量用字符在左边补位将其补到长度为当然这个程序没做充足的参数检查这个就不细说了我们分析一下它的效率如果

作者: @flowmemo
源地址: http://flowmemo.github.io/2016/03/25/str...

最近一个left-pad事件搞得javascript圈沸沸扬扬的. 我们暂且把这个事情放一边, 来看看left-pad本身的实现.

left-pad的源码如下:

module.exports = leftpad;

function leftpad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = " ";
  len = len - str.length;
  while (++i < len) {
    str = ch + str;
  }
  return str;
}

这段程序的作用是, 给一个字符串str(或可以转成str的变量), 用字符ch在左边补位, 将其补到长度为len.
当然这个程序没做充足的参数检查, 这个就不细说了. 我们分析一下它的效率:
如果要补n位, 字符串加法的执行次数是n次, 也就是O(n).

我们来看一下如何用ES6的String.prototype.repeat来实现这个功能:
假定str是字符串, len是非负整数, ch参数可选(如果有的话必须是长度为1的字符串) 所以我更喜欢强类型语言.

function leftpadES6 (str, len, ch) {
  ch = ch||" "
  return ch.repeat(len - str.length) + str
}

当然还没完, 这么写的效率怎么样呢, 我们得看一下js引擎对String.prototype.repeat的实现.

V8

下面是Chrome/Chromuim的js引擎V8的实现, 直接用js写的
源码地址: https://code.google.com/p/chromium/codes...

// ES6, section 21.1.3.13
function StringRepeat(count) {
  CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat");
  
  var s = TO_STRING(this);
  var n = TO_INTEGER(count);

  if (n < 0 || n === INFINITY) throw MakeRangeError(kInvalidCountValue);

  // Early return to allow an arbitrarily-large repeat of the empty string.
  if (s.length === 0) return "";

  // The maximum string length is stored in a smi, so a longer repeat
  // must result in a range error.
  if (n > %_MaxSmi()) throw MakeRangeError(kInvalidCountValue);

  var r = "";
  while (true) {
    if (n & 1) r += s;
    n >>= 1;
    if (n === 0) return r;
    s += s;
  }
}

忽略参数检查, 我们来关注算法本身. 这个算法的核心是, 使用字符串重复次数count的二进制表示, 通过字符串的自加来减少字符串加法的次数. 这个算法和快速幂算法非常像. 详细的算法解释可以看这篇文章: http://www.2ality.com/2014/01/efficient-...

举例个例子, 如果count = 6 , 那个它的二进制表示就为1102 = 4*1 + 2*1 + 1*0. 也就是说对于长度为6的字符串s, 有
s.repeat(6) = s.repeat(4) + s.repeat(2)

注意到每次循环最多有两次字符串加法的操作, 而循环次数约等于logn, 所以按字符串加法的次数来记它的复杂度为O(logn)

Firefox的实现类似, 地址在这里https://dxr.mozilla.org/mozilla-central/... .

Chakra

好了, 我们来看看微软的Edge浏览器所使用的js引擎, Chakra对String.prototype.repeat的实现, 它是用的C++.
源码地址: https://github.com/Microsoft/ChakraCore/...

Chakra中实现repeat分了两个函数, 一个是JavascriptString::EntryRepeat, 它的主要是做一些初始化工作, 参数检查, 特殊情况的处理.核心算法是JavascriptString::RepeatCore, 代码如下

JavascriptString* JavascriptString::RepeatCore(JavascriptString* currentString, charcount_t count, ScriptContext* scriptContext)
  {
    Assert(currentString != nullptr);
    Assert(currentString->GetLength() > 0);
    Assert(count > 0);

    const char16* currentRawString = currentString->GetString();
    int currentLength = currentString->GetLength();

    charcount_t finalBufferCount = UInt32Math::Add(UInt32Math::Mul(count, currentLength), 1);
    char16* buffer = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, finalBufferCount);

    if (currentLength == 1)
    {
        wmemset(buffer, currentRawString[0], finalBufferCount - 1);
        buffer[finalBufferCount - 1] = "";
    }
    else
    {
        char16* bufferDst = buffer;
        size_t bufferDstSize = finalBufferCount;

        for (charcount_t i = 0; i < count; i += 1)
        {
            js_wmemcpy_s(bufferDst, bufferDstSize, currentRawString, currentLength);
            bufferDst += currentLength;
            bufferDstSize -= currentLength;
        }
        Assert(bufferDstSize == 1);
        *bufferDst = "";
    }

    return JavascriptString::NewWithBuffer(buffer, finalBufferCount - 1, scriptContext);
  }

看起来很长是吗? 不要被吓到了, 我们只关心核心算法, 其实就这一小段:

if (currentLength == 1)
{
    wmemset(buffer, currentRawString[0], finalBufferCount - 1);
    buffer[finalBufferCount - 1] = "";
}
else
{
    char16* bufferDst = buffer;
    size_t bufferDstSize = finalBufferCount;

    for (charcount_t i = 0; i < count; i += 1)
    {
        js_wmemcpy_s(bufferDst, bufferDstSize, currentRawString, currentLength);
        bufferDst += currentLength;
        bufferDstSize -= currentLength;
    }
    Assert(bufferDstSize == 1);
    *bufferDst = "";
}

大意是如果字符串长度本身为1, 也就是一个字符, 那就直接用wmemset(类似于memset)将一块内存全用这个字符填充; 如果不为字符串长度不为1, 就一次连接一个字符串. 忽略js_wmemcpy_s

我们之前说了, V8实现的字符串加法的操作次数是O(logn)的, 但是, 我们要把一个字符串重复n次, 一定要得在要对O(n)的内存进行写操作.
update1: 经过@哦胖茶巨巨的提醒(http://weibo.com/2451315930/DowQCo6wN), V8、ChakraCore 和 Rhino 底层实现字符串拼接用的都是rope(tree). 对于rope来说字符串连接不需要为新字符串开辟被内存并把内容写进去, 而是合并两个二叉树, 详见这里: https://en.wikipedia.org/wiki/Rope_%28da... .

这么看的话, 在不考虑rope摊平的情况下, 仅从算法复杂度的角度来看V8的rope连接和快速幂实现是比Chakra的要好. Chakra里字符串也用了rope, 但是对repeat的实现没有用rope, 直接就是复制内存. 字符串在内存是就是以连续内存的形式存储的话, 把一个字符串重复n次, 一定要得在要对O(n)的内存进行写操作, 所以快速幂优化意义不大.

至于V8的这种字符串加法用rope、js实现用快速幂的方法好, 还是Chakra这种直接复制内存的方法好, 我没有跑benchmark, 就不下结论了.

最后想说的是, 虽然这篇文章关注的是实现算法, 但是参数检查、边界条件的处理, 也非常的重要, 千万不能觉得无所谓. 可以说工具函数中edge case的处理经常比算法更为头疼...

P.S.1 我在知道V8, Chakra的字符串实现用了rope后对本文进行过修改
*P.S.2 我之前搞错了, V8中字符串“+”操作符不依赖String.prototype.concat, 实际正好相反,是String.prototype.concat的实现直接用了字符串“+”运算. 源码: https://code.google.com/p/chromium/codes...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87730.html

相关文章

  • 一些当前 Node.js 中最流行 ES6 特性的 benchmark (V8 / Chakra

    摘要:前言项目地址如果有想要增加的特性,欢迎更新,然后。环境大致结论许多情况下下的特性表现相对更好。 前言 项目 github 地址:https://github.com/DavidCai1993/ES6-benchmark 如果有想要增加的特性 benchmark ,欢迎更新benchmarks/ ,然后 PR 。 环境 CPU: Intel Core(TM) i5-2410M 2.30...

    ZHAO_ 评论0 收藏0
  • javascript排序问题探究

    摘要:通过看源代码可以发现,的数组排序算法实现的也是快速排序。而且相比较于,它就只是实现了纯粹的快速排序,完全没有中的那些性能优化的踪影。 引言 几个月前面试的时候被问过javascript中sort方法的具体算法实现,当时回答的是要看下浏览器引擎的实现,今天看到了EFE关于前端排序的博客,正好学习下 问题描述 我们经常发现不同浏览器的排序结果不同,由于不同引擎在算法选择上的差异,在浏览器中...

    scola666 评论0 收藏0
  • V8 Ignition:JS 引擎与字节码的不解之缘(转载)

    摘要:纵览各个引擎的实现,我们发现基于字节码的实现是主流。引入字节码之后,的性能得到了显著的提升。而这次引入字节码却是向着相反的方向后退。这便是引入字节码的主要动机。如今也回到了字节码的怀抱,不禁令人感叹引擎与字节码真是有着不解之缘 首先贴个Javascript性能测试站点,测试并展示了数个 JavaScript 引擎的性能数据:arewefastyetshowImg(https://seg...

    lijinke666 评论0 收藏0
  • [译]你并不知道Node

    摘要:问题什么是调用栈并且它是的一部分么调用栈当然是的一部分。为什么理解是重要的因为你在每个进程中只能获取一个调用栈。它是一个从事件队列中跳去事件的循环并且将它们的回调压入到调用栈中。当调用栈为空的时候,事件循环可以决定下一步执行哪一个。 你并不知道Node 原文:You don’t know Node 译者:neal1991 welcome to star my articles-tra...

    miqt 评论0 收藏0
  • JavaScript工作原理(二):V8引擎5招高效代码

    摘要:引擎可以用标准解释器或即时编译器来实现,即时编译器以某种形式将代码编译为字节码。这里的主要区别在于不生成字节码或任何中间代码。请注意,不使用中间字节码表示法,不需要解释器。这允许在正常执行期间非常短的暂停。 本系列的第一篇文章重点介绍了引擎,运行时和调用栈的概述。第二篇文章将深入V8的JavaScript引擎的内部。我们还会提供一些关于如何编写更好的JavaScript代码的技巧。 概...

    leone 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<