-1

Python 3.10

I've been tasked with proving I can max out the CPU of my Mac laptop (10 cores) by calling a local API endpoint which runs in a Java VM, as well as "measure and record throughput," all using Python. For parallelization, I have researched and decided to go with asyncio per this answer: https://stackoverflow.com/a/59385935/7191927

I plan to use htop to show all cores maxed out so that part I think I have covered. Where I'm getting tripped up is what I actually need to do in the code to max out the CPU.

This is what I have so far. This code is to call two local API endpoints (which each just process blocks of text and extract relevant terms):

import asyncio
from api import API, DocumentParameters, EndException


def background(f):
    def wrapped(*args, **kwargs):
        return asyncio.get_event_loop().run_in_executor(None, f, *args, **kwargs)
    return wrapped

@background
def get_word_results(data):
    api = API(url='http://localhost:8181/rest/words')     
    words_data = data        
    params = DocumentParameters()
    params["content"] = words_data 
    try:
        result = api.words(params)
    except EndException as exception:
        print(exception)
    return result

@background
def get_language_results(data):
    api = API(url='http://localhost:8181/rest/languages')     
    language_data = data        
    params = DocumentParameters()
    params["content"] = language_text_data 
    try:
        result = api.language(params)
    except EndException as exception:
        print(exception)
    return result

if __name__ == '__main__':
    filepath = "/Users/me/stuff.txt"
    with open(filepath, 'r') as file: 
        data = file.read()
    get_word_results(data)
    get_language_results(data)
    print('Done.')

This is where my Python knowledge/experience begins to wane.

So what would be the most efficient way to:

  1. Run this code continuously and at increasing thread counts in attempt to max out the CPU.
  2. Measure and record throughput, as per the requirement.

EDIT 1 - Bounty started. I need a solid solution for this - which maxes out CPU and gives some kind of output that shows this, as well as how many calls are being made and causing the max. Based on what Mr Miyagi says in the comments, it sound like multiprocessing is what I want, either instead of or in tandem with asyncio The winner will achieve with the lowest amount of lines of code.

EDIT 2 - it must be accomplished using using one script/program, being run once, rather than running the same script multiple times.

Stpete111
  • 3,109
  • 4
  • 34
  • 74
  • What exactly has to "max out the CPU"? Your program or the local API endpoint? ``asyncio`` is for *concurrency*, not parallelization – it only uses a single core *by design*. Similarly, the default ``asyncio`` executor uses threads, which are bound by the GIL – they will also only use a single core. – MisterMiyagi Nov 24 '21 at 13:12
  • Note that if all you are using ``asyncio`` is to run things in threads, you might as well not use ``asyncio``. Python directly exposes ``threading`` and ``multiprocessing`` backends – the latter would be most suitable to actually use several cores. – MisterMiyagi Nov 24 '21 at 13:14
  • @MisterMiyagi thanks for the comments. The idea is to have the local server which runs the API endpoint max out the cpu. Ok so I can implement `multiprocessing` - would I do that in place of `asyncio` or in-tandem? And how would I go about actually running this in such a way that it goes in a continuous and increasing manner, and then actually record results? – Stpete111 Nov 24 '21 at 13:41
  • It's still not clear to me what part should max out the CPU. The question only shows two separate API endpoints, so doing more than two things at once just seems not possible. If you just want to hammer the same endpoints endlessly and concurrently, that's what [``Executor.submit``](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Executor.submit) is there for, for example. If you want the CPUs to do some actual work, that's difficult to predict without knowing the API; similarly, we don't know what throughput to measure – requests, items, words, bytes, ...? – MisterMiyagi Nov 29 '21 at 10:48
  • The CPU should be maxed by the calls to and responses from the API endpoints - so yes - hammering the two endpoints endlessly and concurrently would be what we would want to go for. We need to measure throughput in request count. So, a simple example - run the program by "hammering the endpoints endlessly and concurrently" - I will manually watch the meters to see when all cores are maxed out. When I stop the program, I will get an output either in the console or to a log file which tells me how many requests had been sent to each endpoint at the time the CPU maxed out. – Stpete111 Nov 29 '21 at 14:49
  • These endpoints are somewhat CPU-intensive by nature in that they use a lot of CPU when processing the text. Therefore, I don't think it's going to take a crazy-high amount of concurrent calls to max out the CPU. – Stpete111 Nov 29 '21 at 14:51

2 Answers2

1

Using asyncio is a good choice. I would pair that with aiohttp to make the calls to the java service. Here is a skeleton script with an awaitable test() method that will allow you to run as many calls as you want, with as many of those calls running in parallel as you want:

import asyncio
import aiohttp

async def test():
  # examples: will run a total of 'task_count' calls limited to 'concurrency' running in parallel 
  task_count = 1000
  concurrency = 10

  async with aiohttp.ClientSession('http://localhost:8181') as session:
    semaphore = asyncio.Semaphore(concurrency)
    tasks = [asyncio.ensure_future(call_service(session, semaphore, task_id)) for task_id in range(0, task_count)]
    await asyncio.gather(*tasks)

async def call_service(session, semaphore, task_id):
  async with semaphore:

    # start your timer here

    async with session.get('/rest/words') as resp:
      await resp.text()
      # stop your timer here and log/accumulate stats

The principal here is that a list of async tasks are created that can potentially execute all at the same time. However, you want to know how many it takes to max out your CPU so a concurrency parameter is provided that allows you to throttle how many calls happen at the same time by using a semaphore.

I've commented where you can add your statements to start measuring the timings.

Andy Brown
  • 11,766
  • 2
  • 42
  • 61
  • Thanks so much for providing a real answer Andy. I will test this later today. As per the requirement, would you be able to add some example code for the “log/accumulate stats” part? I know how to instantiate timers, but need guidance on the recording of stats. – Stpete111 Dec 02 '21 at 13:29
  • Hi again Andy - I was wondering if you might be available to meet me in chat, as I have a couple questions about your solution? – Stpete111 Dec 02 '21 at 16:17
-1

For a quick way just run the same python file multiple times. To max just run 20 python scripts at once or something.

byteface
  • 152
  • 2
  • 7
  • I’ve added EDIT 2 to my post to clarify that this would not meet the requirement. – Stpete111 Nov 29 '21 at 10:37
  • no worries. Just if you were stuck. it's only a few lines of bash to kick of a python script loads of times. just get the program to accept args and pass in batches. takes 2 mins. – byteface Nov 29 '21 at 22:26