Этот вопрос очень похож на 400 потоков в 20 процессах, превосходящих 400 потоков в 4 процессах при выполнении задачи, связанной с вводом / выводом .Единственное отличие состоит в том, что связанный вопрос касается задачи, связанной с вводом-выводом, тогда как этот вопрос касается задачи, связанной с процессором.
Экспериментальный код
Вот экспериментальный код, который можно запуститьуказанное число рабочих процессов, а затем запустите указанное число рабочих потоков внутри каждого процесса и выполните задачу вычисления n-го простого числа.
import math
import multiprocessing
import random
import sys
import time
import threading
def main():
processes = int(sys.argv[1])
threads = int(sys.argv[2])
tasks = int(sys.argv[3])
# Start workers.
in_q = multiprocessing.Queue()
process_workers = []
for _ in range(processes):
w = multiprocessing.Process(target=process_worker, args=(threads, in_q))
w.start()
process_workers.append(w)
start_time = time.time()
# Feed work.
for nth in range(1, tasks + 1):
in_q.put(nth)
# Send sentinel for each thread worker to quit.
for _ in range(processes * threads):
in_q.put(None)
# Wait for workers to terminate.
for w in process_workers:
w.join()
total_time = time.time() - start_time
task_speed = tasks / total_time
print('{:3d} x {:3d} workers => {:6.3f} s, {:5.1f} tasks/s'
.format(processes, threads, total_time, task_speed))
def process_worker(threads, in_q):
thread_workers = []
for _ in range(threads):
w = threading.Thread(target=thread_worker, args=(in_q,))
w.start()
thread_workers.append(w)
for w in thread_workers:
w.join()
def thread_worker(in_q):
while True:
nth = in_q.get()
if nth is None:
break
num = find_nth_prime(nth)
#print(num)
def find_nth_prime(nth):
# Find n-th prime from scratch.
if nth == 0:
return
count = 0
num = 2
while True:
if is_prime(num):
count += 1
if count == nth:
return num
num += 1
def is_prime(num):
for i in range(2, int(math.sqrt(num)) + 1):
if num % i == 0:
return False
return True
if __name__ == '__main__':
main()
Вот как я запускаю эту программу:
python3 foo.py <PROCESSES> <THREADS> <TASKS>
Например, python3 foo.py 20 20 2000
создает 20 рабочих процессов с 20 потоками в каждом рабочем процессе (таким образом, в общей сложности 400 рабочих потоков) и выполняет 2000 задач.В конце эта программа печатает, сколько времени потребовалось для выполнения задач и сколько задач в среднем она выполняла в секунду.
Среда
Я тестирую этот код на виртуальной машине Linodeчастный сервер с 8 ГБ оперативной памяти и 4 процессорами.Он работает под управлением Debian 9.
$ cat /etc/debian_version
9.9
$ python3
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
$ free -m
total used free shared buff/cache available
Mem: 7987 67 7834 10 85 7734
Swap: 511 0 511
$ nproc
4
Пример 1: 20 процессов x 20 потоков
Вот несколько пробных запусков с 400 рабочими потоками, распределенными между 20 рабочими процессами (то есть 20 рабочими потокамив каждом из 20 рабочих процессов).
Вот результаты:
$ python3 bar.py 20 20 2000
20 x 20 workers => 12.702 s, 157.5 tasks/s
$ python3 bar.py 20 20 2000
20 x 20 workers => 13.196 s, 151.6 tasks/s
$ python3 bar.py 20 20 2000
20 x 20 workers => 12.224 s, 163.6 tasks/s
$ python3 bar.py 20 20 2000
20 x 20 workers => 11.725 s, 170.6 tasks/s
$ python3 bar.py 20 20 2000
20 x 20 workers => 10.813 s, 185.0 tasks/s
Когда я отслеживаю использование ЦП с помощью команды top
, я вижу, что каждый python3
рабочийПроцесс потребляет от 15% до 25% процессорного времени.
Случай 2: 4 процесса x 100 потоков
Теперь я подумал, что у меня всего 4 процессора.Даже если я запускаю 20 рабочих процессов, в любой момент времени физически может работать не более 4 процессов.Кроме того, из-за глобальной блокировки интерпретатора (GIL) только один поток в каждом процессе (таким образом, максимум 4 потока) может работать в любой момент физического времени.
Поэтому я подумал, что если я уменьшу числочисло процессов до 4 и увеличьте число потоков на один процесс до 100, так что общее число потоков все еще останется равным 400, производительность не должна ухудшаться.
Но результаты теста показывают, что 4 процесса, содержащих по 100 потоков каждыйстабильно работают хуже, чем 20 процессов, каждый из которых содержит 20 потоков.
$ python3 bar.py 4 100 2000
4 x 100 workers => 19.840 s, 100.8 tasks/s
$ python3 bar.py 4 100 2000
4 x 100 workers => 22.716 s, 88.0 tasks/s
$ python3 bar.py 4 100 2000
4 x 100 workers => 20.278 s, 98.6 tasks/s
$ python3 bar.py 4 100 2000
4 x 100 workers => 19.896 s, 100.5 tasks/s
$ python3 bar.py 4 100 2000
4 x 100 workers => 19.876 s, 100.6 tasks/s
Загрузка ЦП составляет от 50% до 66% для каждого python3
рабочего процесса.
Случай 3: 1 Процесс x400 потоков
Для сравнения, я записываю тот факт, что и случай 1, и случай 2 превосходят тот случай, когда у нас есть все 400 потоков в одном процессе.Это, очевидно, связано с глобальной блокировкой интерпретатора (GIL).
$ python3 bar.py 1 400 2000
1 x 400 workers => 34.762 s, 57.5 tasks/s
$ python3 bar.py 1 400 2000
1 x 400 workers => 35.276 s, 56.7 tasks/s
$ python3 bar.py 1 400 2000
1 x 400 workers => 32.589 s, 61.4 tasks/s
$ python3 bar.py 1 400 2000
1 x 400 workers => 33.974 s, 58.9 tasks/s
$ python3 bar.py 1 400 2000
1 x 400 workers => 35.429 s, 56.5 tasks/s
Загрузка ЦП составляет от 110% до 115% для одного рабочего процесса python3
.
Случай 4:400 процессов x 1 поток
Опять же, просто для сравнения, вот как выглядят результаты при 400 процессах, каждый из которых имеет один поток.
$ python3 bar.py 400 1 2000
400 x 1 workers => 8.814 s, 226.9 tasks/s
$ python3 bar.py 400 1 2000
400 x 1 workers => 8.631 s, 231.7 tasks/s
$ python3 bar.py 400 1 2000
400 x 1 workers => 10.453 s, 191.3 tasks/s
$ python3 bar.py 400 1 2000
400 x 1 workers => 8.234 s, 242.9 tasks/s
$ python3 bar.py 400 1 2000
400 x 1 workers => 8.324 s, 240.3 tasks/s
Загрузка ЦП находится между 1% к 3% для каждого python3
рабочего процесса.
Сводка
При выборе медианного результата для каждого случая мы получаем следующее резюме:
Case 1: 20 x 20 workers => 12.224 s, 163.6 tasks/s
Case 2: 4 x 100 workers => 19.896 s, 100.5 tasks/s
Case 3: 1 x 400 workers => 34.762 s, 57.5 tasks/s
Case 4: 400 x 1 workers => 8.631 s, 231.7 tasks/s
Вопрос
Почему 20 процессов x 20 потоков работают лучше, чем 4 процесса x 100 потоков, даже если у меня только 4 процессора?
На самом деле, 400 процессов x 1 поток работает лучше, несмотря на наличие только 4процессоры?Почему?