zoukankan      html  css  js  c++  java
  • ES6 Proxy 性能之我见

    ES6 Proxy 性能之我见

    本文翻译自https://thecodebarbarian.com/thoughts-on-es6-proxies-performance

    Proxy是ES6的一个强力功能,它通过为 get/set一个属性 设置"陷阱"(函数处理器)让我们可以拦截对于对象属性的操作。比如:

    const obj = {};
    const proxy = new Proxy(obj, {
      get: () => {
        console.log('hi');
      }
    });
    
    obj.a; // "hi"
    

    Proxy被称赞为现在已经被废弃的Object.observe()属性的取代者

    然而不幸的是,Proxy有一个致命缺陷:性能。

    更打击人的是,Object.observe()就是因为性能被废弃的,而以我(原作者)对V8的理解,对于JIT(Just in Time,准时制)来说,Object.observe()比Proxy容易优化多了。

    Proxy到底有多慢?

    我(原作者)在node v6.9.0中用benchmark简单试了一下:

    var Benchmark = require('benchmark');
    
    var suite = new Benchmark.Suite;
    
    var obj = {};
    
    var _obj = {};
    var proxy = new Proxy(_obj, {
      set: (obj, prop, value) => { _obj[prop] = value; }
    });
    
    var defineProp = {};
    Object.defineProperty(defineProp, 'prop', {
      configurable: false,
      set: v => defineProp._v = v
    });
    
    // 译者注: vanilla js 指的就是原生js
    suite.
      add('vanilla', function() {
        obj.prop = 5;
      }).
      add('proxy', function() {
        proxy.prop = 5;
      }).
      add('defineProperty', function() {
        defineProp.prop = 5;
      }).
      on('cycle', function(event) {
        console.log(String(event.target));
      }).
      on('complete', function() {
        console.log('Fastest is ' + this.filter('fastest').map('name'));
      }).
      run();
    

    结果如下:

    $ node proxy.js
    vanilla x 74,288,023 ops/sec ±0.78% (86 runs sampled)
    proxy x 3,625,152 ops/sec ±2.51% (86 runs sampled)
    defineProperty x 74,815,513 ops/sec ±0.80% (85 runs sampled)
    Fastest is defineProperty,vanilla
    $
    

    从这个简单的benchmark中我们可以看到,Proxy的set 比直接赋值和defineProperty慢非常多(译者注:ops/sec,每秒进行的操作数,越大越快)。

    为防大家好奇,我(原作者)又在node 4.2.1测试了一下Object.observe()

    $ node proxy.js
    vanilla x 78,615,272 ops/sec ±1.55% (84 runs sampled)
    defineProperty x 79,882,188 ops/sec ±1.31% (85 runs sampled)
    Object.observe() x 5,234,672 ops/sec ±0.86% (89 runs sampled)
    Fastest is defineProperty,vanilla
    

    有些文章可能让你觉得只要Proxy不用get/set而是只设置getOwnPropertyDescriptor()的话,就比其他的快,于是我(原作者)又试了试:

    var _obj = {};
    var propertyDescriptor = {
      configurable: true,
      set: v => { _obj.prop = v; }
    };
    var proxy = new Proxy(_obj, {
      getOwnPropertyDescriptor: (target, prop) => propertyDescriptor
    });
    

    不幸的是,反而更慢了:

    $ node proxy.js
    vanilla x 73,695,484 ops/sec ±1.04% (88 runs sampled)
    proxy x 2,026,006 ops/sec ±0.74% (90 runs sampled)
    defineProperty x 74,137,733 ops/sec ±1.25% (88 runs sampled)
    Fastest is defineProperty,vanilla
    $
    

    用Proxy包裹一个函数并调用同样比原生的包裹函数并调用慢非常多:

    var Benchmark = require('benchmark');
    
    var suite = new Benchmark.Suite;
    
    var fn = () => 5;
    var proxy = new Proxy(function() {}, {
      apply: (target, context, args) => fn.apply(context, args)
    });
    
    var wrap = () => fn();
    
    // add tests
    suite.
      add('vanilla', function() {
        fn();
      }).
      add('proxy', function() {
        proxy();
      }).
      add('wrap', function() {
        wrap();
      }).
      on('cycle', function(event) {
        console.log(String(event.target));
      }).
      on('complete', function() {
        console.log('Fastest is ' + this.filter('fastest').map('name'));
      }).
      run();
    
    $ node proxy2.js
    vanilla x 78,426,813 ops/sec ±0.93% (88 runs sampled)
    proxy x 5,244,789 ops/sec ±2.17% (87 runs sampled)
    wrap x 75,350,773 ops/sec ±0.85% (85 runs sampled)
    Fastest is vanilla
    

    无用的提升Proxy性能的方法

    目前最有影响力的提升Proxy性能的方法是让被修改的属性的configurable设为false:

    var _obj = {};
    Object.defineProperty(_obj, 'prop', { configurable: false });
    var propertyDescriptor = {
      configurable: false,
      enumerable: true,
      set: v => { _obj.prop = v; }
    };
    var proxy = new Proxy(_obj, {
      getOwnPropertyDescriptor: (target, prop) => propertyDescriptor
    });
    

    (译者注:这段代码有些问题,enumerableconfigurable为false时是无效的)

    $ node proxy.js
    vanilla x 74,622,163 ops/sec ±0.95% (85 runs sampled)
    proxy x 4,649,544 ops/sec ±0.47% (85 runs sampled)
    defineProperty x 77,048,878 ops/sec ±0.60% (88 runs sampled)
    Fastest is defineProperty
    $
    

    要是这样写set/get,还不如直接用 Object.defineProperty()

    这样写的话,你就不得不设置每个你要在Proxy中用到的属性不可配置(not configurable)。

    不然的话,V8就会报错:

    var _obj = {};
    Object.freeze(_obj);
    var propertyDescriptor = {
      configurable: false,
      enumerable: true,
      set: v => { _obj.prop = v; }
    };
    var proxy = new Proxy(_obj, {
      getOwnPropertyDescriptor: (target, prop) => propertyDescriptor
    });
    
    // Throws:
    // "TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned
    // descriptor for property 'prop' that is incompatible with the
    // existing property in the proxy target"
    // 拦截'prop'属性返回的descriptor和target(原对象)已经存在的属性不匹配
    proxy.prop = 5;
    

    Proxy 也要不行了么?

    Proxy比 Object.defineProperty()有不少优点:

    • Proxy 可以嵌套,而Object.defineProperty()getter/setter就不能嵌套,这样你就不需要知道提前知道你要拦截的所有属性
    • 可以拦截数组变化

    但它性能太差了。

    性能有多大影响呢?

    以Promise和回调为例:

    var Benchmark = require('benchmark');
    
    var suite = new Benchmark.Suite;
    
    var handleCb = cb => cb(null);
    
    // add tests
    suite.
      add('new function', function() {
        handleCb(function(error, res) {});
      }).
      add('new promise', function() {
        return new Promise((resolve, reject) => {});
      }).
      add('promise resolve', function() {
        Promise.resolve().then(() => {});
      }).
      on('cycle', function(event) {
        console.log(String(event.target));
      }).
      on('complete', function() {
        console.log('Fastest is ' + this.filter('fastest').map('name'));
      }).
      run();
    
    $ node promise.js
    new function x 26,282,805 ops/sec ±0.74% (90 runs sampled)
    new promise x 1,953,037 ops/sec ±1.02% (86 runs sampled)
    promise resolve x 194,173 ops/sec ±13.80% (61 runs sampled)
    Fastest is new function
    $
    

    Promise也慢了非常多,

    但是 bluebird声称为Promise提供"非常好的性能",测试一下:

    $ node promise.js
    new function x 26,986,342 ops/sec ±0.48% (89 runs sampled)
    new promise x 11,157,758 ops/sec ±1.05% (87 runs sampled)
    promise resolve x 671,079 ops/sec ±27.01% (18 runs sampled)
    Fastest is new function
    

    虽然快了很多,但仍然比回调慢不少。

    所以我们要因此放弃Promise么?

    并不是这样的,很多公司仍然选择了使用Promise。我(原作者)虽然不是很确定,但是Uber好像就在使用Promise。

    结论

    Proxy很慢,但是在你因其性能而放弃它之前,记得同样性能很差的Promise在最近几年中被快速采用。

    如果你想使用代理,很可能你不会感觉到性能的影响,除非你发现自己为了性能的原因改变了Promise库(或者完全避开了它们)。

    更新

    2019.01, 在node v11.3.0中: Promise已经变得足够好, Proxy还是那样

    vanilla x 833,244,386 ops/sec ±0.76% (89 runs sampled)
    proxy x 28,590,800 ops/sec ±0.72% (88 runs sampled)
    wrap x 824,349,552 ops/sec ±0.87% (86 runs sampled)
    Fastest is vanilla,wrap
    new function x 834,121,566 ops/sec ±0.82% (89 runs sampled)
    new promise x 819,789,350 ops/sec ±0.76% (87 runs sampled)
    promise resolve x 1,212,009 ops/sec ±40.98% (30 runs sampled)
    Fastest is new function
    
  • 相关阅读:
    2019-2020-1 20175228 实验四 外设驱动程序设计
    2019-2020-1 20175228 实验三 实时系统
    2019-2020-1-20175332 20175323 20175228 实验一开发环境的熟悉
    2018-2019-2 20175228实验五《Java网络编程》实验报告
    2018-2019-2 20175228实验四《Android开发基础》实验报告
    2018-2019-2 20175228实验三《敏捷开发与XP实践》实验报告
    MyCP
    2018-2019-2 20175228实验二《面向对象程序设计》实验报告
    2018-2019-2 20175228实验一《Java开发环境的熟悉》实验报告
    转()析构函数
  • 原文地址:https://www.cnblogs.com/zmj97/p/10954968.html
Copyright © 2011-2022 走看看