Параллельная обработка вместо широких потоков
Замеры: распознавание даёт x52 на 4 потоках против x13 на 16, разделение говорящих x33 против x8. Дальше четырёх потоков синхронизация съедает весь выигрыш, поэтому ядра занимаются несколькими задачами сразу. По умолчанию 4 потока на задачу и до 4 задач параллельно. Захват задачи из очереди стал атомарным - без этого два воркера брали одну и ту же. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e3b4e3c73f
commit
be837bab0b
@@ -0,0 +1,97 @@
|
||||
"""Тесты параллельной обработки.
|
||||
|
||||
Замеры показали: обе стадии упираются в 4 потока, а на 16 работают вчетверо
|
||||
медленнее. Значит ядра нужно занимать не шириной одной задачи, а несколькими
|
||||
задачами сразу - и тогда очередь обязана быть устойчивой к гонкам.
|
||||
"""
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
|
||||
from app.store import JobStatus, JobStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store(tmp_path):
|
||||
return JobStore(tmp_path / "jobs.db")
|
||||
|
||||
|
||||
class TestClaimIsAtomic:
|
||||
def test_claim_marks_running(self, store):
|
||||
job_id = store.create(filename="a.wav", duration_sec=1.0)
|
||||
assert store.claim_next() == job_id
|
||||
assert store.get(job_id)["status"] == JobStatus.RUNNING
|
||||
|
||||
def test_second_claim_gets_nothing(self, store):
|
||||
store.create(filename="a.wav", duration_sec=1.0)
|
||||
store.claim_next()
|
||||
assert store.claim_next() is None
|
||||
|
||||
def test_each_job_claimed_once_under_load(self, store):
|
||||
"""Главное требование: два воркера не должны взять одну задачу."""
|
||||
ids = {store.create(filename=f"{i}.wav", duration_sec=1.0) for i in range(50)}
|
||||
claimed: list[str] = []
|
||||
lock = threading.Lock()
|
||||
|
||||
def worker():
|
||||
while True:
|
||||
job_id = store.claim_next()
|
||||
if job_id is None:
|
||||
return
|
||||
with lock:
|
||||
claimed.append(job_id)
|
||||
|
||||
threads = [threading.Thread(target=worker) for _ in range(8)]
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
assert len(claimed) == len(set(claimed)) == 50
|
||||
assert set(claimed) == ids
|
||||
|
||||
def test_claims_oldest_first(self, store):
|
||||
first = store.create(filename="1.wav", duration_sec=1.0)
|
||||
store.create(filename="2.wav", duration_sec=1.0)
|
||||
assert store.claim_next() == first
|
||||
|
||||
|
||||
class TestWorkerSettings:
|
||||
def test_default_threads_is_four_not_all_cores(self, tmp_path):
|
||||
"""Широкие потоки замедляют обе стадии, поэтому по умолчанию их немного."""
|
||||
from app.config import load_settings
|
||||
|
||||
config = tmp_path / "config.toml"
|
||||
config.write_text('[processing]\nthreads=0\n', encoding="utf-8")
|
||||
assert load_settings(config).effective_threads() == 4
|
||||
|
||||
def test_explicit_threads_respected(self, tmp_path):
|
||||
from app.config import load_settings
|
||||
|
||||
config = tmp_path / "config.toml"
|
||||
config.write_text('[processing]\nthreads=6\n', encoding="utf-8")
|
||||
assert load_settings(config).effective_threads() == 6
|
||||
|
||||
def test_workers_derived_from_cores(self, tmp_path, monkeypatch):
|
||||
from app.config import load_settings
|
||||
|
||||
monkeypatch.setattr("os.cpu_count", lambda: 32)
|
||||
config = tmp_path / "config.toml"
|
||||
config.write_text('[processing]\nthreads=4\nworkers=0\n', encoding="utf-8")
|
||||
# 32 логических ядра при 4 потоках на задачу - но не больше разумного предела
|
||||
assert 2 <= load_settings(config).effective_workers() <= 4
|
||||
|
||||
def test_workers_never_below_one(self, tmp_path, monkeypatch):
|
||||
from app.config import load_settings
|
||||
|
||||
monkeypatch.setattr("os.cpu_count", lambda: 1)
|
||||
config = tmp_path / "config.toml"
|
||||
config.write_text('[processing]\nthreads=4\nworkers=0\n', encoding="utf-8")
|
||||
assert load_settings(config).effective_workers() == 1
|
||||
|
||||
def test_explicit_workers_respected(self, tmp_path):
|
||||
from app.config import load_settings
|
||||
|
||||
config = tmp_path / "config.toml"
|
||||
config.write_text('[processing]\nworkers=3\n', encoding="utf-8")
|
||||
assert load_settings(config).effective_workers() == 3
|
||||
Reference in New Issue
Block a user