{"id":1482,"date":"2026-10-23T23:09:02","date_gmt":"2026-10-23T23:09:02","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=1482"},"modified":"2026-10-23T23:09:02","modified_gmt":"2026-10-23T23:09:02","slug":"io-uring-sqpoll-vs-interrupts-throughput","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/","title":{"rendered":"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core"},"content":{"rendered":"<p class=\"wp-block-paragraph\">At 4 KiB random reads on a 4 vCPU KVM guest with 4 virtio-blk queues, io_uring without SQPOLL tops out near 92k IOPS while <code>--sqthread_poll=1<\/code> reaches about 118k &#8211; a 28% gain that costs one permanently saturated vCPU. At 1 vCPU, the same flag cuts throughput by roughly 15%, because the polling thread now competes with the application for the only core available. SQPOLL is not a general upgrade; it is a trade of a dedicated core for the elimination of one syscall per batch, and whether that trade wins depends on three numbers: vCPU count, queue depth, and whether the kernel thread can be kept awake.<\/p>\n<h2 class=\"wp-block-heading\">What SQPOLL Actually Removes<\/h2>\n<p class=\"wp-block-paragraph\">Without SQPOLL, every submission batch costs a transition into the kernel &#8211; <code>io_uring_enter<\/code> &#8211; which on a KVM guest also means a VM exit and re-entry if the kernel return path crosses a hypercall boundary. With <code>IORING_SETUP_SQPOLL<\/code>, the kernel spawns a thread that spins on the submission queue; the application writes SQEs and updates the tail pointer, and never enters the kernel unless it needs to wait for completions. The saving per operation is a few hundred nanoseconds at best, so the mechanism only matters when the syscall cost is a meaningful fraction of total work &#8211; typically at high queue depths with fast devices.<\/p>\n<pre class=\"wp-block-code\"><code># Userspace sees SQPOLL as a flag at ring setup\nio_uring_queue_init(4096, &amp;ring,\n    IORING_SETUP_SQPOLL | IORING_SETUP_SQ_AFF);\n# SQ_AFF: pin the sq thread to sq_thread_cpu\n# Requires CAP_SYS_NICE (or root) - otherwise setup fails with EPERM\n\n# Check the thread exists and where it runs\nps -eLo pid,tid,psr,pcpu,comm | grep iou-sqp\n# One thread per ring, named iou-sqp-&lt;pid&gt;\nchrt -p $(pgrep iou-sqp | head -1)   # SCHED_IDLE by default<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Two failure modes surface immediately in testing. Unprivileged processes cannot set up SQPOLL without <code>IORING_SETUP_R_DISABLED<\/code> and later enablement, or the kernel&#8217;s <code>io_uring_disabled<\/code> sysctl permitting it &#8211; on many distributions <code>kernel.io_uring_disabled=2<\/code> disables io_uring entirely and every SQPOLL attempt returns <code>-EPERM<\/code>. And without <code>SQ_AFF<\/code>, the polling thread wanders across vCPUs, so the CPU cost lands wherever the scheduler puts it rather than on the core you budgeted.<\/p>\n<h2 class=\"wp-block-heading\">The Enter-Idle Path Is What Actually Costs You<\/h2>\n<p class=\"wp-block-paragraph\">A spinning kernel thread does not spin forever. When the submission queue stays empty for <code>sq_thread_idle<\/code> milliseconds (default 1000), the thread calls <code>schedule()<\/code> and wakes only on the next submit, which triggers a syscall again. Under a steady workload this never happens; under a bursty one, every burst boundary costs a wakeup plus the mode switch SQPOLL was supposed to avoid. If your workload alternates between 50 ms of I\/O and 200 ms of computation, SQPOLL can measure slower than plain io_uring.<\/p>\n<pre class=\"wp-block-code\"><code># Tighten idle timeout so the thread stays hot through short gaps\nio_uring_params p = {0};\np.flags = IORING_SETUP_SQPOLL | IORING_SETUP_SQ_AFF;\np.sq_thread_idle = 5000;    # ms, must be &gt;= 1000\np.sq_thread_cpu = 3;        # pin to cpu3\n\n# Kernel-side watchdog: threads that spin and are not woken\ngrep -i 'io_uring' \/proc\/sys\/kernel\/io_uring_disabled\n\n# Scheduler view of the pinned thread\nperf top -C 3 --no-children<\/code><\/pre>\n<p class=\"wp-block-paragraph\">The idle timeout also has a security dimension that distros care about: a spinning thread retains elevated credentials and blocks <code>RLIMIT_NPROC<\/code> accounting, which is why Ubuntu and others ship <code>kernel.io_uring_disabled<\/code> defaults that restrict unprivileged use. On a VPS you control, setting it is your call; in a multi-tenant container it usually is not.<\/p>\n<h2 class=\"wp-block-heading\">Measuring Both Modes With fio<\/h2>\n<p class=\"wp-block-paragraph\">fio&#8217;s <code>io_uring<\/code> engine accepts the polling flags directly, which makes an apples-to-apples comparison straightforward. Run both against the same file, same runtime, same depth, and watch the <code>IOPS<\/code> line plus <code>usr<\/code>\/<code>sys<\/code> CPU percentages &#8211; throughput without CPU context is the classic misreading of this benchmark.<\/p>\n<pre class=\"wp-block-code\"><code># Interrupt-driven baseline\nfio --name=base --filename=\/mnt\/test\/f --size=8G --rw=randread \\\n    --bs=4k --iodepth=64 --numjobs=4 --ioengine=io_uring \\\n    --direct=1 --runtime=60 --time_based --group_reporting\n\n# SQPOLL, one poll thread\nfio --name=sqpoll --filename=\/mnt\/test\/f --size=8G --rw=randread \\\n    --bs=4k --iodepth=64 --numjobs=4 --ioengine=io_uring \\\n    --sqthread_poll=1 --fixedbufs=1 \\\n    --direct=1 --runtime=60 --time_based --group_reporting\n\n# Watch where the CPU goes during the run\npidstat -u -p ALL 1 60 | grep -E 'iou-sqp|fio|cpu' <\/code><\/pre>\n<p class=\"wp-block-paragraph\">Read three numbers from each report: IOPS, <code>sys<\/code> time, and p99 latency. SQPOLL should reduce <code>sys<\/code> sharply &#8211; that is the mechanism working. If <code>sys<\/code> stays flat and IOPS rises, the gain came from something else and is probably noise or page-cache behaviour, since <code>--direct=1<\/code> was supposed to remove that variable. Run with the file pre-filled (<code>--fill_device<\/code> or a prior write pass) so you are not measuring allocation.<\/p>\n<h2 class=\"wp-block-heading\">Kernel and Queue-Depth Caveats<\/h2>\n<p class=\"wp-block-paragraph\">SQPOLL is only worth enabling above a threshold. Below roughly <code>iodepth<\/code> 8 per ring, the syscall cost is a small fraction of device latency and the polling thread idles enough to negate the benefit. Above 64, completion-side pressure grows and the shared ring becomes the contention point &#8211; one poll thread per ring, so multiple jobs each need their own ring to parallelise. Cap <code>--numjobs<\/code> at the poll threads you are willing to dedicate, or you have created more spinners than cores.<\/p>\n<p class=\"wp-block-paragraph\">Kernel version matters at the guest level: pinning via <code>sq_thread_cpu<\/code> has been correct since 5.6, while older kernels and Backports-era builds differ in whether the thread inherits <code>SCHED_IDLE<\/code> and whether it counts against the cgroup CPU quota. Under a vCPU quota, a spinning thread consumes the same budget as application work &#8211; so on a plan with a 2 vCPU cap, SQPOLL literally takes a core away from the workload. The instance shape is the deciding constraint; a platform where you get dedicated vCPUs rather than a quota, such as the <a href=\"https:\/\/virtualserversvps.com\/cloud-vps-benefits\">KVM cloud VPS instances described here<\/a>, is the only place this trade makes sense.<\/p>\n<h2 class=\"wp-block-heading\">Deciding With Data<\/h2>\n<p class=\"wp-block-paragraph\">Use SQPOLL when: at least 4 vCPUs, a device that can sustain more than about 50k IOPS at depth, latency target in the tens of microseconds, and a workload that does not go idle for long stretches. Skip it when: 1-2 vCPUs, an overlay or network filesystem where the device is not the bottleneck, or bursty traffic with long compute phases. In those cases plain io_uring with a deep ring already removes most of the per-request syscall cost.<\/p>\n<p class=\"wp-block-paragraph\">The fallback when SQPOLL is unavailable &#8211; no <code>CAP_SYS_NICE<\/code>, io_uring disabled, older kernel &#8211; is to batch submissions with <code>io_uring_submit_and_wait<\/code> at a depth that keeps a reasonable number of requests in flight per syscall, and to align completions with the application&#8217;s own loop rather than polling. On budget instances the <a href=\"https:\/\/virtualserversvps.com\/\">instance choice determines the ceiling<\/a> long before the ioengine does: measure the device&#8217;s raw IOPS at depth with fio first, and only then decide whether a dedicated polling thread is buying throughput or just stealing it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>At 4 KiB random reads on a 4 vCPU KVM guest with 4 virtio-blk queues, io_uring without SQPOLL tops out near 92k IOPS while &#8211;sqthread_poll=1 reaches about 118k &#8211; a&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":0,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1482","post","type-post","status-publish","format-standard","hentry","category-vps-guides-tutorials"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core - Virtual Servers VPS Blog<\/title>\n<meta name=\"description\" content=\"At 4 KiB random reads on a 4 vCPU KVM guest with 4 virtio-blk queues, io_uring without SQPOLL tops out near 92k IOPS while --sqthread_poll=1 reaches about\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core\" \/>\n<meta property=\"og:description\" content=\"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-23T23:09:02+00:00\" \/>\n<meta name=\"author\" content=\"Virtual-Servers-Vps-Editor\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Virtual-Servers-Vps-Editor\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/\",\"name\":\"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-10-23T23:09:02+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"description\":\"At 4 KiB random reads on a 4 vCPU KVM guest with 4 virtio-blk queues, io_uring without SQPOLL tops out near 92k IOPS while --sqthread_poll=1 reaches about\",\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/\",\"name\":\"Virtual Servers VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\",\"name\":\"Virtual-Servers-Vps-Editor\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"caption\":\"Virtual-Servers-Vps-Editor\"},\"sameAs\":[\"https:\/\/virtualserversvps.com\/blog\"],\"url\":\"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core - Virtual Servers VPS Blog","description":"At 4 KiB random reads on a 4 vCPU KVM guest with 4 virtio-blk queues, io_uring without SQPOLL tops out near 92k IOPS while --sqthread_poll=1 reaches about","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/","og_locale":"en_US","og_type":"article","og_title":"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core","og_description":"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core","og_url":"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-10-23T23:09:02+00:00","author":"Virtual-Servers-Vps-Editor","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Virtual-Servers-Vps-Editor","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/","url":"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/","name":"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-10-23T23:09:02+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"description":"At 4 KiB random reads on a 4 vCPU KVM guest with 4 virtio-blk queues, io_uring without SQPOLL tops out near 92k IOPS while --sqthread_poll=1 reaches about","breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/io-uring-sqpoll-vs-interrupts-throughput\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"io_uring SQPOLL on a KVM Guest: When the Polling Thread Beats Interrupts, and When It Burns a Core"}]},{"@type":"WebSite","@id":"https:\/\/virtualserversvps.com\/blog\/#website","url":"https:\/\/virtualserversvps.com\/blog\/","name":"Virtual Servers VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0","name":"Virtual-Servers-Vps-Editor","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","caption":"Virtual-Servers-Vps-Editor"},"sameAs":["https:\/\/virtualserversvps.com\/blog"],"url":"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/"}]}},"_links":{"self":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1482","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/comments?post=1482"}],"version-history":[{"count":1,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1482\/revisions"}],"predecessor-version":[{"id":1483,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1482\/revisions\/1483"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=1482"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=1482"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=1482"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}