fast-abi icon indicating copy to clipboard operation
fast-abi copied to clipboard

Perf

Open dekz opened this issue 4 years ago • 0 comments

import { expect } from '@0x/contracts-test-utils';
import { AbiEncoder, BigNumber, NULL_ADDRESS } from '@0x/utils';
import { MethodAbi } from 'ethereum-types';
import { utils } from 'ethers';
// import { utils as ethers5Utils } from 'ethers5';
import _ = require('lodash');
import { performance, PerformanceObserver } from 'perf_hooks';

const percentile = require('percentile');
// tslint:disable-next-line: no-implicit-dependencies
import { FastABI } from 'fast-abi';

import { ZERO_AMOUNT } from '../../src/utils/market_operation_utils/constants';
import { getSampleAmounts } from '../../src/utils/market_operation_utils/sampler';

describe.only('Encoder perf', () => {
    const UNISWAP_V2_SELL_ABI: MethodAbi = {
        inputs: [
            {
                internalType: 'address',
                name: 'router',
                type: 'address',
            },
            {
                internalType: 'address[]',
                name: 'path',
                type: 'address[]',
            },
            {
                internalType: 'uint256[]',
                name: 'takerTokenAmounts',
                type: 'uint256[]',
            },
        ],
        name: 'sampleSellsFromUniswapV2',
        outputs: [
            {
                internalType: 'uint256[]',
                name: 'makerTokenAmounts',
                type: 'uint256[]',
            },
        ],
        stateMutability: 'view',
        type: 'function',
    };

    const KYBER_TUPLE_ABI: MethodAbi = {
        inputs: [
            {
                name: 'opts',
                type: 'tuple',
                components: [
                    {
                        name: 'reserveOffset',
                        type: 'uint256',
                    },
                    {
                        name: 'hintHandler',
                        type: 'address',
                    },
                    {
                        name: 'networkProxy',
                        type: 'address',
                    },
                    {
                        name: 'weth',
                        type: 'address',
                    },
                    {
                        name: 'hint',
                        type: 'bytes',
                    },
                ],
            },
            {
                name: 'takerToken',
                type: 'address',
            },
            {
                name: 'makerToken',
                type: 'address',
            },
            {
                name: 'takerTokenAmounts',
                type: 'uint256[]',
            },
        ],
        name: 'sampleSellsFromKyberNetwork',
        outputs: [
            {
                name: 'reserveId',
                type: 'bytes32',
            },
            {
                name: 'hint',
                type: 'bytes',
            },
            {
                name: 'makerTokenAmounts',
                type: 'uint256[]',
            },
        ],
        stateMutability: 'view',
        type: 'function',
    };

    const BATCH_CALL_ABI: MethodAbi = {
        inputs: [
            {
                name: 'callDatas',
                type: 'bytes[]',
            },
        ],
        name: 'batchCall',
        outputs: [
            {
                name: 'callResults',
                type: 'tuple[]',
                components: [
                    {
                        name: 'data',
                        type: 'bytes',
                    },
                    {
                        name: 'success',
                        type: 'bool',
                    },
                ],
            },
        ],
        stateMutability: 'view',
        type: 'function',
    };

    const RUST_ENCODER = new FastABI([UNISWAP_V2_SELL_ABI, BATCH_CALL_ABI, KYBER_TUPLE_ABI], { BigNumber });

    // tslint:disable: custom-no-magic-numbers
    const RUNS = 10000;
    // const RUNS = 1;
    const ADDRESS_1 = '0x6b175474e89094c44da98b954eedeac495271d0f';
    const ADDRESS_2 = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
    let summary: { p25: string; p50: string; p99: string; p100: string };
    const perf = (fn: () => void): any => {
        const wrapped = performance.timerify(fn);
        const resultsMs: number[] = [];
        const obs = new PerformanceObserver(list => resultsMs.push(list.getEntries()[0].duration));
        obs.observe({ entryTypes: ['function'] });
        _.times(RUNS, () => wrapped());
        obs.disconnect();
        summary = {
            p25: percentile(25, resultsMs),
            p50: percentile(50, resultsMs),
            p99: percentile(99, resultsMs),
            p100: percentile(100, resultsMs),
        };
    };

    before(() => {
        console.log('Runs:', RUNS);
    });
    beforeEach(() => {
        summary = { p25: '0', p50: '0', p99: '0', p100: '0' };
    });
    afterEach(() => {
        const { p25, p50, p99, p100 } = summary;
        console.log(`p25: ${p25}ms, p50: ${p50}ms, p99: ${p99}ms, p100: ${p100}ms\n`);
    });

    const TIMEOUT = 360000;
    const ZERO_EX_ENCODER = new AbiEncoder.Method(UNISWAP_V2_SELL_ABI);
    const ZERO_EX_UNOPTIMIZED: AbiEncoder.EncodingRules = { shouldOptimize: false, shouldAnnotate: false };
    const ZERO_EX_OPTIMIZED: AbiEncoder.EncodingRules = { shouldOptimize: true, shouldAnnotate: false };

    const ETHERS_INTERFACE = new utils.Interface([UNISWAP_V2_SELL_ABI]);
    const ETHERS_ENCODER = ETHERS_INTERFACE.functions[UNISWAP_V2_SELL_ABI.name];

    // const ETHERS_5_INTERFACE = new ethers5Utils.Interface([UNISWAP_V2_SELL_ABI]);

    describe.only('hello', () => {
        it('node', () => {
            const f = () => 'hello world';
            perf(f);
        });
        it.only('rust', () => {
            const f = () => FastABI.ping();
            perf(f);
        });
        it.only('rust - encode', () => {
            const params = [ADDRESS_1, [ADDRESS_2], [1, 2, 3]];
            const output = RUST_ENCODER.encodeInput('sampleSellsFromUniswapV2', params);
        });
    });

    describe.only('Tuple ABI', () => {
        const ZERO_EX_TUPLE = new AbiEncoder.Method(KYBER_TUPLE_ABI);
        const params = [
            {
                reserveOffset: ZERO_AMOUNT,
                hintHandler: NULL_ADDRESS,
                networkProxy: NULL_ADDRESS,
                weth: NULL_ADDRESS,
                hint: '0x',
            },
            ADDRESS_1,
            ADDRESS_2,
            getSampleAmounts(new BigNumber(100e6), 13, 1.03),
        ];
        const encoded = ZERO_EX_TUPLE.encode(params, ZERO_EX_UNOPTIMIZED);
        describe('encode', () => {
            it('rust', () => {
                const f = () => RUST_ENCODER.encodeInput('sampleSellsFromKyberNetwork', params);
                expect(f()).to.eq(encoded);
                perf(f);
            });
            it('zeroex - optimized', () => {
                const f = () => ZERO_EX_TUPLE.encode(params, ZERO_EX_OPTIMIZED);
                perf(f);
            });
            it('zeroex - unoptimized', () => {
                const f = () => ZERO_EX_TUPLE.encode(params, ZERO_EX_UNOPTIMIZED);
                perf(f);
            });
        });

        describe('decode', () => {
            it('rust', () => {
                const f = () => RUST_ENCODER.decodeInput('sampleSellsFromKyberNetwork', encoded);
            });
        });
    });

    describe('Uniswap ABI', () => {
        [13, 130].forEach(numSamples => {
            describe(`${numSamples} input encode`, () => {
                const params: [string, string[], BigNumber[]] = [
                    ADDRESS_1, // router
                    [ADDRESS_1, ADDRESS_2, ADDRESS_1], // path
                    getSampleAmounts(new BigNumber(100e6), numSamples, 1.03),
                ];

                it.only('ZeroEx - optimized', () => {
                    const f = () => ZERO_EX_ENCODER.encode(params, ZERO_EX_OPTIMIZED);
                    perf(f);
                }).timeout(TIMEOUT);

                it.only('ZeroEx - no optimize', () => {
                    const f = () => ZERO_EX_ENCODER.encode(params, ZERO_EX_UNOPTIMIZED);
                    perf(f);
                }).timeout(TIMEOUT);

                it('ZeroEx - BigInt - optimize', () => {
                    const amounts = params[2].map(n => BigInt(n.toString()));
                    const f = () => ZERO_EX_ENCODER.encode([params[0], params[1], amounts], ZERO_EX_OPTIMIZED);
                    perf(f);
                }).timeout(TIMEOUT);

                it('ZeroEx - BigInt - no optimize', () => {
                    const amounts = params[2].map(n => BigInt(n.toString()));
                    const f = () => ZERO_EX_ENCODER.encode([params[0], params[1], amounts], ZERO_EX_UNOPTIMIZED);
                    perf(f);
                }).timeout(TIMEOUT);

                it.only('rust', () => {
                    const f = () => RUST_ENCODER.encodeInput('sampleSellsFromUniswapV2', params);
                    perf(f);
                    expect(f()).to.eq(ZERO_EX_ENCODER.encode(params, ZERO_EX_UNOPTIMIZED));
                }).timeout(TIMEOUT);

                it.skip('Ethers', () => {
                    const f = () => ETHERS_ENCODER.encode(params);
                    perf(f);
                });
                // it.skip('Ethers 5', () => {
                //     const f = () => ETHERS_5_INTERFACE.encodeFunctionData(UNISWAP_V2_SELL_ABI.name, params);
                //     perf(f);
                // }).timeout(TIMEOUT);
            });

            describe.only(`${numSamples} input decode`, () => {
                const params: [string, string[], BigNumber[]] = [
                    ADDRESS_1, // router
                    [ADDRESS_1, ADDRESS_2, ADDRESS_1], // path
                    getSampleAmounts(new BigNumber(100e6), numSamples, 1.03),
                ];
                const data = ZERO_EX_ENCODER.encode(params, ZERO_EX_UNOPTIMIZED);
                it('ZeroEx', () => {
                    const f = () => ZERO_EX_ENCODER.decode(data);
                    perf(f);
                }).timeout(TIMEOUT);

                it('rust', () => {
                    const f = () => RUST_ENCODER.decodeInput('sampleSellsFromUniswapV2', data);
                    perf(f);
                }).timeout(TIMEOUT);
            });

            describe.only(`${numSamples} output decode`, () => {
                const params = getSampleAmounts(new BigNumber(100e6), numSamples, 1.03);
                const data = ZERO_EX_ENCODER.encodeReturnValues([params], ZERO_EX_UNOPTIMIZED);
                it('ZeroEx', () => {
                    const f = () => ZERO_EX_ENCODER.decodeReturnValues(data);
                    perf(f);
                }).timeout(TIMEOUT);

                it('rust', () => {
                    const f = () => RUST_ENCODER.decodeOutput('sampleSellsFromUniswapV2', data);
                    perf(f);
                }).timeout(TIMEOUT);
            });
        });
    });

    describe('BatchCall ABI', () => {
        [10, 50, 100].forEach(numSamples => {
            describe.only(`${numSamples} batchCall`, () => {
                const callParams: [string, string[], BigNumber[]] = [
                    ADDRESS_1, // router
                    [ADDRESS_1, ADDRESS_2, ADDRESS_1], // path
                    getSampleAmounts(new BigNumber(100e6), 13, 1.03),
                ];

                const encodedBatchCall = ZERO_EX_ENCODER.encode(callParams, ZERO_EX_UNOPTIMIZED);
                const params = _.times(numSamples, () => encodedBatchCall);

                const BATCH_CALL_ENCODER = new AbiEncoder.Method(BATCH_CALL_ABI);

                it('rust', () => {
                    const f = () => RUST_ENCODER.encodeInput('batchCall', [params]);
                    expect(f()).to.eq(BATCH_CALL_ENCODER.encode([params], ZERO_EX_UNOPTIMIZED));
                    perf(f);
                }).timeout(TIMEOUT);

                it('ZeroEx - optimized', () => {
                    const f = () => BATCH_CALL_ENCODER.encode([params], ZERO_EX_OPTIMIZED);
                    perf(f);
                }).timeout(TIMEOUT);

                it('ZeroEx - no optimize', () => {
                    const f = () => BATCH_CALL_ENCODER.encode([params], ZERO_EX_UNOPTIMIZED);
                    perf(f);
                }).timeout(TIMEOUT);
            });
        });
    });
});

dekz avatar May 07 '21 04:05 dekz