Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perf #2

Open
dekz opened this issue May 7, 2021 · 0 comments
Open

Perf #2

dekz opened this issue May 7, 2021 · 0 comments

Comments

@dekz
Copy link
Member

dekz commented May 7, 2021

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);
            });
        });
    });
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant