简体   繁体   English

为什么Object.create比构造函数慢得多?

[英]Why is Object.create so much slower than a constructor?

Background 背景

In a project I'm maintaining we make extensive use of null prototype objects as a poor man's alternative to (string key only) Maps, which are not natively supported in many older, pre-ES6 browsers. 在一个项目中,我维护我们广泛使用null原型对象作为穷人的替代(仅限字符串键)映射,这些映射在许多较旧的ES6之前的浏览器中不是本机支持的。

Basically, to create a null prototype object on the fly, one would use: 基本上,要动态创建一个null原型对象,可以使用:

var foo = Object.create(null);

This guarantees that the new object has no inherited properties, such as "toString", "constructor", "__proto__" which are not desirable for this particular use case. 这保证了新对象没有继承属性,例如“toString”,“constructor”,“__ proto__”,这对于这个特定的用例是不可取的。

Since this pattern appears multiple times in code, we came up with the idea of writing a constructor that would create objects whose prototype has a null prototype and no own properties. 由于这种模式在代码中多次出现,我们提出了编写构造函数的想法,该构造函数将创建其原型具有null原型且没有自己的属性的对象。

var Empty = function () { };
Empty.prototype = Object.create(null);

Then to create an object with no own or inherited properties one can use: 然后,要创建一个没有自己或继承属性的对象,可以使用:

var bar = new Empty;

The problem 问题

In a strive to improve performance, I wrote a test, and found that the native Object.create approach unexpectedly performs much slower than the method involving an extra constructor with an ad hoc prototype, in all browsers: http://jsperf.com/blank-object-creation . 为了提高性能,我编写了一个测试,发现在所有浏览器中,本机Object.create方法意外地执行的速度比涉及带有ad hoc原型的额外构造函数的方法慢得多: http//jsperf.com/空白对象创建

I was ingenuously expecting the latter method to be slower as it involves invoking a user defined constructor, which doesn't happen in the former case. 我非常期待后一种方法更慢,因为它涉及调用用户定义的构造函数,这在前一种情况下不会发生。

What could be the cause of such a performance difference? 造成这种性能差异的原因是什么?

You've been investigating something which is highly dependent on the specific version of the browser you are running. 您一直在调查一些高度依赖于您运行的浏览器的特定版本的内容。 Here are some results I get here when I run your jsperf test: 以下是我在运行jsperf测试时得到的一些结果:

  • In Chrome 47 new Empty runs at 63m ops/sec whereas Object.create(null) runs at 10m ops/sec. 在Chrome 47中, new Empty运行速度为63m ops / sec,而Object.create(null)运行速度为10m ops / sec。

  • In Firefox 39 new Empty runs at 733m ops/sec whereas Object.create(null) runs at 1,685m ops/sec. 在Firefox 39中, new Empty以733m ops / sec运行,而Object.create(null)以1,685m ops / sec运行。

("m" above means we're talking about millions.) (上面的“m”表示我们谈论的是数百万。)

So which one do you pick? 那你选哪一个? The method which is fastest in one browser is slowest in the other. 在一个浏览器中最快的方法在另一个浏览器中最慢。

Not only this, but the results we are looking at here are very likely to change with new browser releases. 不仅如此, 我们在这里看到的结果很可能随着新的浏览器版本而改变。 Case in point, I've checked the implementation of Object.create in v8. 例如,我已经检查了v8中Object.create的实现。 Up to December 30th 2015, the implementation of Object.create was written in JavaScript, but a commit recently changed it to a C++ implementation. 截至2015年12月30日, Object.create的实现是用JavaScript编写的,但最近提交将其更改为C ++实现。 Once this makes its way into Chrome, the results of comparing Object.create(null) and new Empty are going to change. 一旦进入Chrome,比较Object.create(null)new Empty结果将会发生变化。

But this is not all... 但这并不是全部...

You've looked at only one aspect of using Object.create(null) to create an object that is going to be used as a kind of map (a pseudo-map). 您只关注使用Object.create(null)来创建一个将用作一种地图(伪地图)的对象。 What about access times into this pseudo-map? 那个伪地图的访问时间怎么样? Here is a test that checks the performance of misses and one that checks the performance of hits . 这是一个检查未命中性能的测试,以及一个检查命中性能的测试。

  • On Chrome 47 both the hits and miss cases are 90% faster with an object created with Object.create(null) . 在Chrome 47上,使用Object.create(null)创建的对象,命中和未命中情况的速度提高了90%。

  • On Firefox 39, the hit cases all perform the same. 在Firefox 39上,命中案例都执行相同的操作。 As for the miss cases, the performance of an object created with Object.create(null) is so great that jsperf tells me the number of ops/sec is "Infinity". 至于未命中的情况,使用Object.create(null)创建的对象的性能是如此之大,以至于jsperf告诉我ops / sec的数量是“Infinity”。

The results obtained with Firefox 39 are those I was actually expecting. 使用Firefox 39获得的结果是我实际期待的结果。 The JavaScript engine should seek the field in the object itself. JavaScript引擎应该在对象本身中寻找字段。 If it is a hit, then the search is over, no matter how the object was created. 如果它是一个命中,那么无论对象是如何创建的,搜索都会结束。 If there is a miss on finding the field in the object itself, then the JavaScript engine must check in the object's prototype. 如果在对象本身中找不到字段,则JavaScript引擎必须检入对象的原型。 In the case of objects created with Object.create(null) , there is no prototype so the search ends there. 对于使用Object.create(null)创建的对象,没有原型,因此搜索在那里结束。 In the case of objects created with new Empty , there is a prototype, in which the JavaScript engine must search. 对于使用new Empty创建的对象,有一个原型,JavaScript引擎必须在其中进行搜索。

Now, in the life-time of a pseudo-map how often is the pseudo-map created? 现在,在伪地图的生命周期中,伪地图的创建频率是多少? How often is it being accessed? 它被访问的频率是多少? Unless you are in a really peculiar situation the map should be created once, but accessed many times. 除非你处于一个非常特殊的情况,否则地图应该创建一次,但多次访问。 So the relative performance of hits and misses is going to be more important to the overall performance of your application, then the relative performance of the various means of creating the object. 因此,命中和未命中的相对性能对应用程序的整体性能更重要,然后是创建对象的各种方法的相对性能。

We could also look at the performance of adding and deleting keys from these pseudo-maps, and we'd learn more. 我们还可以查看从这些伪映射中添加和删除键的性能,我们将了解更多信息。 Then again, maybe you have maps from which you never remove keys (I've got a few of those) so deletion performance may not be important for your case. 然后,也许你有地图,你从来没有删除密钥(我有一些),所以删除性能可能对你的情况不重要。

Ultimately, what you should be profiling to improve the performance of your application is your application as a system . 最终,您应该为提高应用程序性能而进行分析的是您作为系统的应用程序。 In this way, the relative importance of the various operations in your actual application is going to be reflected in your results. 通过这种方式, 您实际应用中各种操作的相对重要性将反映在您的结果中。

The performance difference has to do with the fact that constructor functions are highly optimized in most JS engines. 性能差异与构造函数在大多数JS引擎中高度优化的事实有关。 There's really no practical reason that Object.create couldn't be as fast as constructor functions, it's just an implementation-dependent thing that will likely improve as time goes on. 实际上没有任何实际的原因,Object.create不能像构造函数一样快,它只是一个依赖于实现的东西,随着时间的推移可能会有所改善。

That being said, all the performance test proves is that you shouldn't be choosing one or the other based on performance because the cost of creating an object is ridiculously low. 话虽如此,所有性能测试证明,您不应该根据性能选择其中一个,因为创建对象的成本非常低。 How many of these maps are you creating? 你创建了多少这些地图? Even the slowest implementation of Object.create on the tests is still chugging out over 8,000,000 objects per second, so unless you have a compelling reasons to create millions of maps, I'd just choose the most obvious solution. 即使是在测试中最慢的Object.create实现仍然每秒超过8,000,000个对象,所以除非你有令人信服的理由创建数百万个地图,否则我只选择最明显的解决方案。

Furthermore, consider the fact that one browser implementation can literally be 100s of times faster than another implementation. 此外,考虑一个浏览器实现可以比另一个实现快100倍的事实。 This difference is going to exists regardless of which you pick, so the small difference between Object.create and constructors shouldn't really be considered a relevant difference within broader context of different implementations. 无论您选择哪种方式,这种差异都将存在,因此Object.create和构造函数之间的微小差异不应该被视为不同实现的更广泛上下文中的相关差异。

Ultimately, Object.create(null) is the obvious solution. 最终,Object.create(null)是显而易见的解决方案。 If the performance of creating objects becomes a bottleneck, then maybe consider using constructors, but even then I would look elsewhere before I resorted to using something like Empty constructors. 如果创建对象的性能成为瓶颈,那么可以考虑使用构造函数,但即便如此,在使用类似Empty构造函数之前,我会先查看其他地方。

This question is pretty much invalid, because jsperf is broken, it skews results for whatever reason. 这个问题几乎无效,因为jsperf被破坏了,它会因任何原因而扭曲结果。 I checked it personally when I was making my own map implementation ( one based on integers ). 我在制作自己的地图实现时(我的一个基于整数)亲自检查了它。

There is purely no difference between these two methods. 这两种方法之间完全没有区别。

BTW I think this an easier way to create an empty object with the same syntax: BTW我认为这是一种使用相同语法创建空对象的更简单方法:

var EmptyV2 = function() { return Object.create(null); };

I wrote my little own test that prints the time to create whatever amount of these 3 methods. 我写了我自己的小测试,打印时间来创建这三种方法的任何数量。

Here it is: 这里是:

<!DOCTYPE html>
<html>
    <head>
        <style>
            html
            {
                background-color: #111111;
                color: #2ECC40;
            }
        </style>
    </head>
    <body>
    <div id="output">

    </div>

    <script type="text/javascript">
        var Empty = function(){};
        Empty.prototype = Object.create(null);

        var EmptyV2 = function() { return Object.create(null); };

        var objectCreate = Object.create;

        function createEmpties(iterations)
        {           
            for(var i = 0; i < iterations; i++)
            {           
                var empty = new Empty();
            }
        }

        function createEmptiesV2(iterations)
        {       
            for(var i = 0; i < iterations; i++)
            {
                var empty = new EmptyV2();
            }
        }

        function createNullObjects(iterations)
        {       
            for(var i = 0; i < iterations; i++)
            {
                var empty = objectCreate(null);
            }
        }

        function addResult(name, start, end, time)
        {           
            var outputBlock = document.getElementsByClassName("output-block");

            var length = (!outputBlock ? 0 : outputBlock.length) + 1;
            var index = length % 3;

            console.log(length);
            console.log(index);

            var output = document.createElement("div");
            output.setAttribute("class", "output-block");
            output.setAttribute("id", ["output-block-", index].join(''));
            output.innerHTML = ["|", name, "|", " started: ", start, " --- ended: ", end, " --- time: ", time].join('');

            document.getElementById("output").appendChild(output);

            if(!index)
            {
                var hr = document.createElement("hr");
                document.getElementById("output").appendChild(hr);
            }
        }

        function runTest(test, iterations)
        {
            var start = new Date().getTime();

            test(iterations);

            var end = new Date().getTime();

            addResult(test.name, start, end, end - start);
        }

        function runTests(tests, iterations)
        {
            if(!tests.length)
            {
                if(!iterations)
                {
                    return;
                }

                console.log(iterations);

                iterations--;

                original = [createEmpties, createEmptiesV2, createNullObjects];

                var tests = [];

                for(var i = 0; i < original.length; i++)
                {
                    tests.push(original[i]);
                }
            }

            runTest(tests[0], 10000000000/8);

            tests.shift();

            setTimeout(runTests, 100, tests, iterations);
        }

        runTests([], 10);
    </script>
    </body>
</html>

I am sorry, it is a bit rigid. 对不起,这有点僵硬。 Just paste it into an index.html and run. 只需将其粘贴到index.html中即可运行。 I think this method of testing is far superior to jsperf. 我认为这种测试方法远远优于jsperf。

Here are my results: 这是我的结果:

|createEmpties| | createEmpties | started: 1451996562280 --- ended: 1451996563073 --- time: 793 开始:1451996562280 ---结束:1451996563073 ---时间:793
|createEmptiesV2| | createEmptiesV2 | started: 1451996563181 --- ended: 1451996564033 --- time: 852 开始:1451996563181 ---结束:1451996564033 ---时间:852
|createNullObjects| | createNullObjects | started: 1451996564148 --- ended: 1451996564980 --- time: 832 开始:1451996564148 ---结束:1451996564980 ---时间:832


|createEmpties| | createEmpties | started: 1451996565085 --- ended: 1451996565926 --- time: 841 开始:1451996565085 ---结束:1451996565926 ---时间:841
|createEmptiesV2| | createEmptiesV2 | started: 1451996566035 --- ended: 1451996566863 --- time: 828 开始:1451996566035 ---结束:1451996566863 ---时间:828
|createNullObjects| | createNullObjects | started: 1451996566980 --- ended: 1451996567872 --- time: 892 开始:1451996566980 ---结束:1451996567872 ---时间:892

|createEmpties| | createEmpties | started: 1451996567986 --- ended: 1451996568839 --- time: 853 开始:1451996567986 ---结束:1451996568839 ---时间:853
|createEmptiesV2| | createEmptiesV2 | started: 1451996568953 --- ended: 1451996569786 --- time: 833 开始:1451996568953 ---结束:1451996569786 ---时间:833
|createNullObjects| | createNullObjects | started: 1451996569890 --- ended: 1451996570713 --- time: 823 开始:1451996569890 ---结束:1451996570713 ---时间:823

|createEmpties| | createEmpties | started: 1451996570825 --- ended: 1451996571666 --- time: 841 开始:1451996570825 ---结束:1451996571666 ---时间:841
|createEmptiesV2| | createEmptiesV2 | started: 1451996571776 --- ended: 1451996572615 --- time: 839 开始:1451996571776 ---结束:1451996572615 ---时间:839
|createNullObjects| | createNullObjects | started: 1451996572728 --- ended: 1451996573556 --- time: 828 开始:1451996572728 ---结束:1451996573556 ---时间:828

|createEmpties| | createEmpties | started: 1451996573665 --- ended: 1451996574533 --- time: 868 开始:1451996573665 ---结束:1451996574533 ---时间:868
|createEmptiesV2| | createEmptiesV2 | started: 1451996574646 --- ended: 1451996575476 --- time: 830 开始:1451996574646 ---结束:1451996575476 ---时间:830
|createNullObjects| | createNullObjects | started: 1451996575582 --- ended: 1451996576427 --- time: 845 开始:1451996575582 ---结束:1451996576427 ---时间:845

|createEmpties| | createEmpties | started: 1451996576535 --- ended: 1451996577361 --- time: 826 开始:1451996576535 ---结束:1451996577361 ---时间:826
|createEmptiesV2| | createEmptiesV2 | started: 1451996577470 --- ended: 1451996578317 --- time: 847 开始:1451996577470 ---结束:1451996578317 ---时间:847
|createNullObjects| | createNullObjects | started: 1451996578422 --- ended: 1451996579256 --- time: 834 开始:1451996578422 ---结束:1451996579256 ---时间:834

|createEmpties| | createEmpties | started: 1451996579358 --- ended: 1451996580187 --- time: 829 开始:1451996579358 ---结束:1451996580187 ---时间:829
|createEmptiesV2| | createEmptiesV2 | started: 1451996580293 --- ended: 1451996581148 --- time: 855 开始:1451996580293 ---结束:1451996581148 ---时间:855
|createNullObjects| | createNullObjects | started: 1451996581261 --- ended: 1451996582098 --- time: 837 开始:1451996581261 ---结束:1451996582098 ---时间:837

|createEmpties| | createEmpties | started: 1451996582213 --- ended: 1451996583071 --- time: 858 开始:1451996582213 ---结束:1451996583071 ---时间:858
|createEmptiesV2| | createEmptiesV2 | started: 1451996583179 --- ended: 1451996583991 --- time: 812 开始:1451996583179 ---结束:1451996583991 ---时间:812
|createNullObjects| | createNullObjects | started: 1451996584100 --- ended: 1451996584948 --- time: 848 开始:1451996584100 ---结束:1451996584948 ---时间:848

|createEmpties| | createEmpties | started: 1451996585052 --- ended: 1451996585888 --- time: 836 开始:1451996585052 ---结束:1451996585888 ---时间:836
|createEmptiesV2| | createEmptiesV2 | started: 1451996586003 --- ended: 1451996586839 --- time: 836 开始:1451996586003 ---结束:1451996586839 ---时间:836
|createNullObjects| | createNullObjects | started: 1451996586954 --- ended: 1451996587785 --- time: 831 开始:1451996586954 ---结束:1451996587785 ---时间:831

|createEmpties| | createEmpties | started: 1451996587891 --- ended: 1451996588754 --- time: 863 开始:1451996587891 ---结束:1451996588754 ---时间:863
|createEmptiesV2| | createEmptiesV2 | started: 1451996588858 --- ended: 1451996589702 --- time: 844 开始:1451996588858 ---结束:1451996589702 ---时间:844
|createNullObjects| | createNullObjects | started: 1451996589810 --- ended: 1451996590640 --- time: 830 开始:1451996589810 ---结束:1451996590640 ---时间:830

In a strive to improve performance, I wrote a test, and found that the native Object.create approach unexpectedly performs much slower than the method involving an extra constructor with an ad hoc prototype, in all browsers 为了提高性能,我编写了一个测试,并发现原生的Object.create方法在所有浏览器中意外执行的速度比涉及带有ad hoc原型的额外构造函数的方法要慢得多

I was ingenuously expecting the latter method to be slower as it involves invoking a user defined constructor, which doesn't happen in the former case. 我非常期待后一种方法更慢,因为它涉及调用用户定义的构造函数,这在前一种情况下不会发生。

Your reasoning postulates that the new operator and Object.create have to use the same inner "object creation" code, with an extra call to the custom constructor for new . 你的推理假定new运算符和Object.create必须使用相同的内部“对象创建”代码,并额外调用new的自定义构造函数。 That's why you find the test result surprising, because you think you're comparing A+B with A. 这就是为什么你发现测试结果令人惊讶的原因,因为你认为你在比较A + B和A.

But that's not true, you shouldn't assume that much about the implementations of new and Object.create . 但事实并非如此,你不应该对newObject.create的实现有太多假设。 Both can resolve to different JS or "native" (mostly C++) and your custom constructor can easily be optimized away by the parser. 两者都可以解析为不同的JS或“本机”(主要是C ++),并且解析器可以轻松地优化您的自定义构造函数。

Beside curiosity, as others have well explained, the empty object creation is a bad focus point for optimizing the entire application - unless you've got some full scale profiling data proving otherwise. 除了好奇心,正如其他人已经很好地解释的那样,空对象创建是优化整个应用程序的一个不好的焦点 - 除非你有一些完整的分析数据证明不是这样。

If you're really worried about objet creation time, add a counter for the number of objects created, increment it in your Empty constructor, log the number of objects created in the lifetime of the program, multiply by the slowest browser execution time, and see (most probably) how negligible creation time is. 如果您真的担心objet创建时间,请为创建的对象数添加一个计数器,在Empty构造函数中递增它,记录在程序生命周期中创建的对象数,乘以最慢的浏览器执行时间,以及看(最有可能)创造时间是多么微不足道。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM