Skip to content

Perf #2

Open
Open
@dekz

Description

@dekz
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);
            });
        });
    });
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions