Skip to content

Revised threading system #3105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Oct 26, 2022
Merged

Revised threading system #3105

merged 39 commits into from
Oct 26, 2022

Conversation

jcupitt
Copy link
Member

@jcupitt jcupitt commented Oct 20, 2022

done:

  • add rgb mode to openslideload
  • new thread recycler
  • threadpools size dynamically with load
  • operations can hint threadpool size
  • add VIPS_MAX_THREADS

todo:

  • doc comments
  • test wasm compatibility

just a set of threads that get recycled

"ninja test" passes and dzsave seems to work, though pytest fails for
some reason
move base stuff into thread.c, so we have a simple

	thread -> threadset -> threadpool

layering
based on the original commit
nice low systime again

still need to repply cnages to threadpool.c since 80e0cc3
so threadpool.c is now up to date
a bit less confusing
based on counting the number of blocked threads in each pool

it works, but the improvement is not great :(
so operators can hint threadpool size (dzsave especially)
since flatten was taking 20% of CPU time for dzsave
now actually works
in the new openslideload rgb mode
oops, turned it off by mistake
kleisauke added a commit to kleisauke/wasm-vips that referenced this pull request Oct 22, 2022
Copy link
Member

@kleisauke kleisauke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! I left some comments inline and was able to resolve the LSan CI failures by adding libMagickCore to the suppression file (commit kleisauke@5338676). The sink->sslock also no longer seems necessary after this (commit kleisauke@b584140).

I had a quick go to test this implementation in wasm-vips, see commit kleisauke/wasm-vips@da8956e. The trick there is to initialize a fixed thread pool during vips_threadset_new() and not let vips_threadset_run() spawn more threads, see the corresponding patch and branch. Though, this didn't seem to work, it somehow exhausts the fixed thread pool. I'll investigate.

jcupitt and others added 4 commits October 22, 2022 14:07
Co-authored-by: Kleis Auke Wolthuizen <github@kleisauke.nl>
Co-authored-by: Kleis Auke Wolthuizen <github@kleisauke.nl>
@kleisauke
Copy link
Member

I confirm that fixes it.

I had a go ... now there's VIPS_MAX_THREADS to preallocate the threadset, and set a hard limit. What do you think? Allocation happens on first use, is that OK for wasm?

Thanks! For Wasm, it needs to setup the fixed threadpool during vips_init(), so I moved it to vips__threadpool_init() instead with commit kleisauke/wasm-vips@ee410bf.

wasm-vips had previously reverted commit 6946c3b for this purpose, but that probably wouldn't work on macOS, where we need to delay setting up the threadpool to ensure fork() safety.

I wonder if we should expose VIPS_MAX_THREADS? As far as I know, WebAssembly is the only platform that requires this, so I'm not sure if we should include it by default, we could perhaps #ifdef __EMSCRIPTEN__ it instead.

Note that running the wasm-vips test suite still seems to exhaust the fixed threadpool, see for example this run:
https://github.com/kleisauke/wasm-vips/actions/runs/3312785036/jobs/5470249864

I'm not sure what's causing this, the previous GLib threadpool implementation didn't had this issue. It can be reproduced by running the test suite on the reimplement-threadpool-wasm branch or with:

$ export VIPS_MAX_THREADS="$(($(nproc) + 3))"
$ echo $VIPS_MAX_THREADS
9

(+3 is the amount of background threads required by vips_sink_disc, I'm aware that dzsave may require more threads than this, but I haven't compiled libgsf for Wasm yet)

@kleisauke
Copy link
Member

Ah, according to the g_thread_pool_push() documentation, new threads are only started when the number of currently running threads is less than the maximal allowed number of threads. Otherwise, it stays in the queue until a thread in the pool finishes its previous task.

@jcupitt
Copy link
Member Author

jcupitt commented Oct 24, 2022

I confirm that fixes it.

Hooray!

For Wasm, it needs to setup the fixed threadpool during vips_init(),

Ah I wasn't sure, OK, let's move it. I hoped to delay making the threads until first use to help systems like macOS, but it sounds like that's not possible (or useful).

I wonder if we should expose VIPS_MAX_THREADS?

I was thinking it would be handy for testing, but you're right, not useful for most people outside wasm. We can leave it undocumented and unsupported.

+3 is the amount of background threads required by vips_sink_disc

I think it's two, isn't it? There's one thread for each of the two write behind buffers, plus the main thread (which we don't count). I see:

$ VIPS_MAX_THREADS=5 VIPS_CONCURRENCY=3 vips gaussblur k2.jpg x.jpg 100
$

So concurrency 3 runs with six threads: 3 workers, 2 write-behind, one main thread.

@jcupitt
Copy link
Member Author

jcupitt commented Oct 24, 2022

Otherwise, it stays in the queue until a thread in the pool finishes its previous task.

Perhaps we should add a queue like this to threadset? At the moment it just hard fails. Though I think there's maybe a danger of deadlock, ouch.

@kleisauke
Copy link
Member

kleisauke commented Oct 24, 2022

I think it's two, isn't it?

Oh huh, perhaps I miscounted. Though, this one seems to fail.

$ vips black x.jpg 100 100
$ VIPS_MAX_THREADS=5 VIPS_CONCURRENCY=3 vips autorot x.jpg x2.jpg
VipsThreadset: threadset is exhausted

Commit kleisauke@a11afaa on the wasm-master branch also seems to fail on the test suite. That branch contains the rebased wasm-vips patches applied to the master branch for reference.

Perhaps we should add a queue like this to threadset? At the moment it just hard fails. Though I think there's maybe a danger of deadlock, ouch.

Deadlocks are indeed the symptoms when things don't work out. For example, I also had to apply commit kleisauke@6100d34 and needed to configure libwebp with --disable-threading for Wasm, since this fixed thread limit applies to the entire binary.

@jcupitt
Copy link
Member Author

jcupitt commented Oct 25, 2022

I think that example is running two pipelines:

$ VIPS_MAX_THREADS=7 VIPS_CONCURRENCY=3 vips autorot k2.jpg x.jpg --vips-progress
vips temp-4: 1450 x 2048 pixels, 3 threads, 1450 x 16 tiles, 128 lines in buffer
vips k2.jpg: 1450 x 2048 pixels, 3 threads, 1450 x 16 tiles, 128 lines in buffer
vips k2.jpg: done in 0.0154s          
vips temp-4: done in 0.0304s          

Some libvips threads are optional (eg. when it tries to expand a threadpool), but some must happen or we'll deadlock (eg. it must have a thread watching a write-behind buffer).

We could add API for the optional case. It'd make it a bit more robust against threadset exhaustion.

@kleisauke
Copy link
Member

Ah, you're right. vips_autorot() is a non-sequential operation, so two pipelines are needed. For example, it can also be reproduced with the gaussblur sample when this line is commented out:

operation_class->flags = VIPS_OPERATION_SEQUENTIAL;

I've clarified with commit kleisauke/wasm-vips@acbfff6 why the +3 is needed.

We could add API for the optional case. It'd make it a bit more robust against threadset exhaustion.

GLib threadpool's uses the g_async_queue_* functions for this. Though, I'm not sure if we should do the same, we previously passed max_threads = -1 to g_thread_pool_new(), so a new thread was spawned when no idle threads were available. IIUC, a queue implementation is only handy for a fixed threadpool (VIPS_MAX_THREADS), it probably adds some overhead when there's no hard limit.

It's a bit unfortunate that WebAssembly has this limitation. :(

@jcupitt
Copy link
Member Author

jcupitt commented Oct 26, 2022

I think we're done, let's merge and try living with it for a bit.

Thank you very much for reviewing this thing!

@jcupitt jcupitt merged commit 976db37 into master Oct 26, 2022
kleisauke referenced this pull request in kleisauke/libvips Oct 28, 2022
Upstream-Status: Inappropriate [Emscripten specific]
@kleisauke
Copy link
Member

kleisauke commented Nov 19, 2022

GLib threadpool's uses the g_async_queue_* functions for this. Though, I'm not sure if we should do the same, we previously passed max_threads = -1 to g_thread_pool_new(), so a new thread was spawned when no idle threads were available. IIUC, a queue implementation is only handy for a fixed threadpool (VIPS_MAX_THREADS), it probably adds some overhead when there's no hard limit.

FWIW, I implemented this with commit kleisauke@fe02527 for wasm-vips (only the changes in threadpool.c and threadset.c are interesting). It pushes tasks to the global GAsyncQueue when the threadset is limited with VIPS_MAX_THREADS and exhausted. vips_threadset_work() is slightly modified to pop these tasks from the global queue and process them before the thread becomes idle. This seems to pass the wasm-vips testsuite using:

VIPS_MAX_THREADS="$(($(nproc) + 3))"

I'm not sure if it's worth upstreaming that, since it could easily deadlock when being used in combination with dzsave or when VIPS_MAX_THREADS is lower than 3.

@jcupitt
Copy link
Member Author

jcupitt commented Nov 21, 2022

Oh, interesting.

It'd be great if we could upstream everything and have wasm as a fully supported target. Maybe we could hide the extra code behind ifdef WASM?

I suppose the downside is that libvips would then have code that was difficult to test, and added maintenance effort. What do you think?

@kleisauke
Copy link
Member

I'd like to officially support Wasm as a supported target in libvips. Indeed, you can just #ifdef Wasm-specific code behind __EMSCRIPTEN__ or __wasm__ (if necessary).

Unfortunately, the main blocker for this is GLib, see for example:
GNOME/glib@2.75.0...kleisauke:wasm-vips-2.75.0

I suppose this should be discussed in GLib first, especially commit kleisauke/glib@4ed9ec0 (since that would cause an ABI break). GLib currently deliberately ignores the -Wcast-function-type and -Wbad-function-cast warnings due to this (and I'm aware that running with -fsanitize=cfi-icall is not supported either) - see:
https://gitlab.gnome.org/GNOME/glib/-/blob/2.75.0/meson.build#L483-485
https://gitlab.gnome.org/GNOME/glib/-/blob/2.75.0/meson.build#L502-504

For this reason, I marked the GLib patches as stalled in the "Upstream patches" item at Stable release for wasm-vips, but hopefully I can revisit that in the future.

@jcupitt
Copy link
Member Author

jcupitt commented Nov 21, 2022

Ah I see. They would really hate an ABI break, but I can see it might be worthwhile to open a discussion about it and raise the issue. They might want to delay for glib-3.0.

I did a quick search and couldn't see a statement from the glib maintainers on emscripten support. If they've not taken an official position, perhaps opening a PR with that change would be useful?

@kleisauke
Copy link
Member

kleisauke commented Nov 21, 2022

I mentioned the patches and my intention to upstream them in this comment, but I plan to discuss this also on GNOME's Discourse instance (using the glib tag and possibly the strawman tag for the function pointer cast issues), this to avoid that either the issue or MR (GitLab's meaning of PR) is immediately closed (e.g. like this one 😅).

Though, perhaps I should check the #gtk IRC channel on irc.gnome.org first to see whether the maintainers are interested in this. Hopefully they won't reject this in favor of https://gitlab.gnome.org/GNOME/gjs.

/cc @RReverser you might also be interested in this.

@jcupitt
Copy link
Member Author

jcupitt commented Nov 21, 2022

Ah sensible, yes I remember that PR being closed without comment. How can a PR not be actionable? Ah well.

Sure, discourse is maybe a better place to start discussion. Please post a link here, I'd be interested to read.

@jcupitt jcupitt deleted the reimplement-threadpool branch November 21, 2022 18:50
kleisauke added a commit to kleisauke/libvips that referenced this pull request Aug 24, 2024
kleisauke added a commit to kleisauke/libvips that referenced this pull request Aug 25, 2024
Fixes a regression introduced in commit 9248ab7 (PR libvips#3105).
kleisauke added a commit to kleisauke/libvips that referenced this pull request Aug 25, 2024
Fixes a regression introduced in commit 9248ab7 (PR libvips#3105).
kleisauke added a commit to kleisauke/libvips that referenced this pull request Aug 25, 2024
Fixes a regression introduced in commit 9248ab7 (PR libvips#3105).
kleisauke added a commit that referenced this pull request Aug 25, 2024
Fixes a regression introduced in commit 9248ab7 (PR #3105).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants