Skip to content

zstdmt: give the thread pools a queue so small jobs run in parallel - #4921

Open
JonSnow1807 wants to merge 1 commit into
meta:devfrom
JonSnow1807:mt-pool-queue
Open

JonSnow1807 wants to merge 1 commit into
meta:devfrom
JonSnow1807:mt-pool-queue

Conversation

@JonSnow1807

Copy link
Copy Markdown

What and why

Addresses the first part of #4890. ZSTDMT creates its thread pool with a queue of 0, and so does ZSTD_createThreadPool(). Without a queue, POOL_tryAdd() fails until a worker has taken the previous job, and the prepared job then waits for the oldest job to finish, so small jobs can run one after another. The queue was set to 0 in 2017 (7db5526, “reduced pool queue to 0 to save memory”), when a queued job held its own input buffer. Since #1045 the inputs live in the round buffer, so a queued job holds no buffer.

This gives the internal pool a queue of nbWorkers and ZSTD_createThreadPool(n) a queue of n. It also replaces the internal pool when a context is reused with more workers, since POOL_resize() can’t grow the queue. Pools provided by the application are untouched. Compressed output is byte-identical (all Silesia files, 13 parameter sets including LDM, rsyncable and a shared pool).

Performance, on 2 MiB of dickens (median of 5 processes, -O3):

  • With ZSTDMT_JOBSIZE_MIN set to 128 KiB, level -4, 16 workers: 480 to 2928 MiB/s on an Apple M2 Pro (Apple clang 17), and geomean 1.10× (at most 1.36×) on a Ryzen 9 5950X (gcc 13, pinned), where workers wake up in time more often.
  • Against the best stock setting (512 KiB jobs): 1.62× on the M2 Pro and 1.07× on the 5950X.
  • Stock builds are neutral on average.

A new zstreamtest test checks that a job is accepted while the only worker is busy. It fails on dev and passes with the change.

Checklist

  • I searched open pull requests and issues: this problem is not already being addressed.
  • Bug fix: it comes with a reproducer or a test that fails without the fix.
  • Performance change: numbers from repeated runs are included, with CPU, compiler, flags and data.
  • I stated below whether AI or other automated tools were used.

Tools used

Code drafted by both human and AI, and reviewed by human.

…llel

The internal pool of a ZSTDMT_CCtx, and the pool created by
ZSTD_createThreadPool(), had no queue. POOL_tryAdd() then fails until a
worker has woken up and taken the previous job. When the next job is
prepared faster than that, it is kept as jobReady, the next call makes no
input progress, and ZSTDMT_flushProduced() waits until the oldest job has
finished. With small jobs (ZSTDMT_JOBSIZE_MIN lowered at compile time),
nearly every job then waits for the previous one.

The queue size was reduced to 0 in 2017 to save memory, when a job waiting
in the queue held its own input buffer. Since commit d932fb6 (2018), the
input of every job is in the round buffer, which is allocated in advance
and reused, and a worker gets its output buffer when it starts the job, so a
queued job holds no buffer. The number of jobs in flight is still bounded
by the jobs table and by the round buffer.

- the internal pool gets a queue of nbWorkers jobs. POOL_resize() keeps
  the queue size, so when a context is reused with more workers, its
  internal pool is replaced instead (all its jobs have completed by then).
- ZSTD_createThreadPool(numThreads) gets a queue of numThreads jobs.
- zstreamtest: new test checking that a job is accepted while the only
  worker is busy (fails without this change). The abandoned frame test
  now posts a third job, so that a prepared job that is not yet posted
  is still covered.

The compressed output is unchanged.

Issue meta#4890
@meta-cla meta-cla Bot added the CLA Signed label Nov 11, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant