Degen Code

Degen Code

Share this post

Degen Code
Degen Code
Arbitrum Bot Improvements: Process Pool Calculation Offloading
Copy link
Facebook
Email
Notes
More

Arbitrum Bot Improvements: Process Pool Calculation Offloading

USE ALL THE CORES

May 27, 2023
∙ Paid
7

Share this post

Degen Code
Degen Code
Arbitrum Bot Improvements: Process Pool Calculation Offloading
Copy link
Facebook
Email
Notes
More
Share

The previous post covered process pools for CPU-bound work. I have modified the Arbitrum bot project to utilize this process pool approach. The implementation at the arb helper level is a bit hacky, but the performance improvements too good to delay sharing them.

Much of the bot structure is unchanged, but the activate_arbs coroutine has been modified heavily. The primary improvements are within that coroutine and the three functions that handle arbitrage calculation updates:

  • _activate_and_record

  • _reactivate

  • calculate_arb_in_pool

The first two are private functions within activate_arbs, and the third is a global function that is triggered by the long-running process_onchain_arbs task.

They achieve the fabled multi-core operation by creating tasks via the asyncio loop’s run_in_executor method.

Here is an example:

async def _reactivate(arbs: List):
    loop = asyncio.get_running_loop()

    _tasks = [
        loop.run_in_executor(
            executor=process_pool,
            func=arb_helper.calculate_arbitrage_return_best,
        )
        for arb_helper in arbs
        if arb_helper.auto_update()
    ]

    for task in asyncio.as_completed(_tasks):
        try:
            arb_id, best = await task
        except bot.exceptions.ArbitrageError:
            pass
        except Exception as e:
            print(f"(_reactivate as_completed) catch-all: {e}")
            print(type(e))
        else:
            arb_helper = active_arbs[arb_id]
            arb_helper.best = best

It works by creating tasks, stored inside a _tasks list, that will run the calculate_arbitrage_return_best method inside a process pool. These tasks are managed by the asyncio event loop behind the scenes, and we instruct the function to process them as they are completed using asyncio.as_completed. Then the task is awaited, which will return an arbitrage ID and the best dictionary that the arbitrage helper generated. Or it will raise an exception! Either way, the results are handled appropriately in the except/else blocks.

Since these results are calculated in a separate process pool with a pickled/unpickled object, it loses connection with the original. Thus we need a way to identify which helper the result is associated with, so the return value of calculate_arbitrage_return_best is a tuple containing the helper ID. That helper ID can be used to look up the “in scope” object within the active bot, and those results can be simply updated in place.

The other functions are similar from the perpective of the process pool, but differ slightly in how they handle the results after they are retrieved. Previously-activated arbitrage helpers are moved directly to active_arbs, whereas newly-activated arbitrage helpers are moved to active_arbs and their values recorded in the “whitelist” for faster activation next time. calculate_arb_in_pool is called on already activated arbs, so no special action is taken besides retrieving and updating the results.

By default, this bot is set up to use 16 workers in the process pool (16 CPU cores). If you want to use more or less, edit the constructor in the main coroutine. An empty call to ProcessPoolExecutor() will set the value automatically, appropriate for your system.

arbitrum_cycle_improved.py

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 BowTiedDevil
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share

Copy link
Facebook
Email
Notes
More