From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: cdupontd@redhat.com From: Christophe de Dinechin Message-ID: Mime-Version: 1.0 (Mac OS X Mail 13.4 \(3608.80.23.2.2\)) Subject: Re: [virtio-dev] On doorbells (queue notifications) Date: Thu, 16 Jul 2020 13:25:37 +0200 In-Reply-To: <20200716100051.GC85868@stefanha-x1.localdomain> References: <87r1tdydpz.fsf@linaro.org> <20200715114855.GF18817@stefanha-x1.localdomain> <877dv4ykin.fsf@linaro.org> <20200715154732.GC47883@stefanha-x1.localdomain> <871rlcybni.fsf@linaro.org> <20200716100051.GC85868@stefanha-x1.localdomain> Content-Type: multipart/alternative; boundary="Apple-Mail=_91F4DD19-1EB7-4C8D-AF38-0F390772D1CD" To: Stefan Hajnoczi Cc: =?utf-8?Q?Alex_Benn=C3=A9e?= , virtio-dev@lists.oasis-open.org, Zha Bin , Jing Liu , Chao Peng , Cornelia Huck , Jan Kiszka , "Michael S. Tsirkin" List-ID: --Apple-Mail=_91F4DD19-1EB7-4C8D-AF38-0F390772D1CD Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > On 16 Jul 2020, at 12:00, Stefan Hajnoczi wrote: >=20 > On Wed, Jul 15, 2020 at 05:40:33PM +0100, Alex Benn=C3=A9e wrote: >>=20 >> Stefan Hajnoczi writes: >>=20 >>> On Wed, Jul 15, 2020 at 02:29:04PM +0100, Alex Benn=C3=A9e wrote: >>>> Stefan Hajnoczi writes: >>>>> On Tue, Jul 14, 2020 at 10:43:36PM +0100, Alex Benn=C3=A9e wrote: >>>>>> Finally I'm curious if this is just a problem avoided by the s390 >>>>>> channel approach? Does the use of messages over a channel just avoid= the >>>>>> sort of bouncing back and forth that other hypervisors have to do wh= en >>>>>> emulating a device? >>>>>=20 >>>>> What does "bouncing back and forth" mean exactly? >>>>=20 >>>> Context switching between guest and hypervisor. >>>=20 >>> I have CCed Cornelia Huck, who can explain the lifecycle of an I/O >>> request on s390 channel I/O. >>=20 >> Thanks. >>=20 >> I was also wondering about the efficiency of doorbells/notifications the >> other way. AFAIUI for both PCI and MMIO only a single write is required >> to the notify flag which causes a trap to the hypervisor and the rest of >> the processing. The hypervisor doesn't have the cost multiple exits to >> read the guest state although it obviously wants to be as efficient as >> possible passing the data back up to what ever is handling the backend >> of the device so it doesn't need to do multiple context switches. >>=20 >> Has there been any investigation into other mechanisms for notifying the >> hypervisor of an event - for example using a HYP call or similar >> mechanism? >>=20 >> My gut tells me this probably doesn't make any difference as a trap to >> the hypervisor is likely to cost the same either way because you still >> need to save the guest context before actioning something but it would >> be interesting to know if anyone has looked at it. Perhaps there is a >> benefit in partitioned systems where core running the guest can return >> straight away after initiating what it needs to internally in the >> hypervisor to pass the notification to something that can deal with it? >=20 > It's very architecture-specific. This is something Michael Tsirkin > looked in in the past. He found that MMIO and PIO perform differently on > x86. VIRTIO supports both so the device can be configured optimally. > There was an old discussion from 2013 here: > https://lkml.org/lkml/2013/4/4/299 >=20 > Without nested page tables MMIO was slower than PIO. But with nested > page tables it was faster. >=20 > Another option on x86 is using Model-Specific Registers (for hypercalls) > but this doesn't fit into the PCI device model. (Warning: What I write below is based on experience with very different architectures, both CPU and hypervisor; your mileage may vary) It looks to me like the discussion so far is mostly focused on a "synchrono= us" model where presumably the same CPU is switching context between guest and (host) device emulation. However, I/O devices on real hardware are asynchronous by construction. They do their thing while the CPU processes stuff. So at least theoreticall= y, there is no reason to context switch on the same CPU. You could very well have an I/O thread on some other CPU doing its thing. This allows to do something some of you may have heard me talk about, called "interrupt coalescing". As Stefan noted, this is not always a win, as it may introduce latency. There are at least two cases where this latency really hurts: 1. When the I/O thread is in some kind of deep sleep, e.g. because it was not active recently. Everything from cache to TLB may hit you here, but that normally happens when there isn't much I/O activity, so this case in practice does not hurt that much, or rather it hurts in a case where don't really care. 2. When the I/O thread is preempted, or not given enough cycles to do its stuff. This happens when the system is both CPU and I/O bound, and addressing that is mostly a scheduling issue. A CPU thread could hand-off to a specific I/O thread, reducing that case to the kind of context switch Alex was mentioning, but I'm not sure how feasible it is to implement that on Linux / kvm. In such cases, you have to pay for context switch. I'm not sure if that context switch is markedly more expensive than a "vmexit". On at least that alien architecture I was familiar with, there was little difference be= tween switching to "your" host CPU thread and switching to "another" host I/O thread. But then the context switch was all in software, so we had designed it that way. So let's assume now that you run your device emulation fully in an I/O thread, which we will assume for simplicity sits mostly in host user-space, and your guest I/O code runs in a CPU thread, which we will assume sits mostly in guest user/kernel space. It is possible to share two-way doorbells / IRQ queues on some memory page, very similar to a virtqueue. When you want to "doorbell" your device, you simply write to that page. The device threads picks it up by reading the same page, and posts I/O completions on the same page, with simple memory writes. Consider this I/O exchange buffer as having (at least) a writer and reader index for both doorbells and virtual interrupts. In the explanation below, I will call them "dwi", "dri", "iwi", "iri" for doorbell / interrupt= read and write index. (Note that as a key optimization, you really don't want dwi and dri to be in the same cache line, since different CPUs are going to read and write them) You obviously still need to "kick" the I/O or CPU thread, and we are talking about an IPI here since you don't know which CPU that other thread is sitting on. But the interesting property is that you only need to do that when dwi=3D=3Ddri or iwi=3D=3Diri, because if not, the other sid= e has already been "kicked" and will keep working, i.e. incrementing dri or iri, until it reaches back that state. The real "interrupt coalescing" trick can happen here. In some cases, you can decide to update your dwi or iwi without kicking, as long as you know that you will need to kick later. That requires some heavy cooperation from guest drivers, though, and is a second-order optimization. With a scheme like this, you replace a systematic context switch for each device interrupt with a memory write and a "fire and forget" kick IPI that only happens when the system is not already busy processing I/Os, so that it can be eliminated when the system is most busy. With interrupt coalescing, you can send IPIs at a rate much lower than the actual I/O rate. Not sure how difficult it is to adapt a scheme like this to the current state of qemu / kvm, but I'm pretty sure it works well if you implement it correctly ;-) >=20 > A bigger issue than vmexit latency is device emulation thread wakeup > latency. There is a thread (QEMU, vhost-user, vhost, etc) monitoring the > ioeventfd but it may be descheduled. Its physical CPU may be in a low > power state. I ran a benchmark late last year with QEMU's AioContext > adaptive polling disabled so we can measure the wakeup latency: >=20 > CPU 0/KVM 26102 [000] 85626.737072: kvm:kvm_fast_mmio: > fast mmio at gpa 0xfde03000 > IO iothread1 26099 [001] 85626.737076: syscalls:sys_exit_ppoll: 0x1 > 4 microseconds ------^ >=20 > (I did not manually configure physical CPU power states or use the > idle=3Dpoll host kernel parameter.) >=20 > Each virtqueue kick had 4 microseconds of latency before the device > emulation thread had a chance to process the virtqueue. This means the > maximum I/O Operations Per Second (IOPS) is capped at 250k before > virtqueue processing has even begun! This data is what prompted me to write the above. This 4us seems really long to me. I recall a benchmark where the technique above was reaching at least 400k IOPs for a single VM on a medium-size system (4CPUs (*)).=20 I remember the time I ran this benchmark quite well, because it was just after VMware made a big splash about reaching 100k IOPs: https://blogs.vmware.com/performance/2008/05/100000-io-opera.html. (*) Yes, at the time, 4 CPUs was a medium size system. Don't laugh. >=20 > QEMU AioContext adaptive polling helps here because we skip the vmexit > entirely while the IOThread is polling the vring (for up to 32 > microseconds by default). >=20 > It would be great if more people dig into this and optimize > notifications further. >=20 > Stefan --Apple-Mail=_91F4DD19-1EB7-4C8D-AF38-0F390772D1CD Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8

On 16 Jul 202= 0, at 12:00, Stefan Hajnoczi <stefanha@redhat.com> wrote:

On Wed, Jul 15, 2020 at 05:40:33PM +010= 0, Alex Benn=C3=A9e wrote:

Stefan Hajnoczi <stefanha@redhat.com> writes:

On Wed, Jul 15, 2020 = at 02:29:04PM +0100, Alex Benn=C3=A9e wrote:
Stefan Hajnoczi <stefanha@redhat.com> writes:
On Tue, Jul 14, 2020 at 10:43:36PM +0100, Alex = Benn=C3=A9e wrote:
Final= ly I'm curious if this is just a problem avoided by the s390
= channel approach? Does the use of messages over a channel just avoid thesort of bouncing back and forth that other hypervisors have to = do when
emulating a device?

What does "bouncing back and forth" mean exactly?

Context switching between guest and hypervisor.

I have CCed Cornelia Huck, who can= explain the lifecycle of an I/O
request on s390 channel I/O.=

Thanks.

I was also wondering about the efficiency of doorbells/notifications = the
other way. AFAIUI for both PCI and MMIO only a single wri= te is required
to the notify flag which causes a trap to the = hypervisor and the rest of
the processing. The hypervisor doe= sn't have the cost multiple exits to
read the guest state alt= hough it obviously wants to be as efficient as
possible passi= ng the data back up to what ever is handling the backend
of t= he device so it doesn't need to do multiple context switches.

Has there been any investigation into other mechanisms for = notifying the
hypervisor of an event - for example using a HY= P call or similar
mechanism?

My = gut tells me this probably doesn't make any difference as a trap to
the hypervisor is likely to cost the same either way because you sti= ll
need to save the guest context before actioning something = but it would
be interesting to know if anyone has looked at i= t. Perhaps there is a
benefit in partitioned systems where co= re running the guest can return
straight away after initiatin= g what it needs to internally in the
hypervisor to pass the n= otification to something that can deal with it?
=
It's very architecture-specif= ic. This is something Michael Tsirkin
looked in in the past. He found that MMIO and PIO perform di= fferently on
x86. VIRTI= O supports both so the device can be configured optimally.
There was an old discussion from 2013 = here:
https:= //lkml.org/lkml/2013/4/4/299

Without nested page tables MMIO w= as slower than PIO. But with nested
page tables it was faster.

Another option on x= 86 is using Model-Specific Registers (for hypercalls)
but this doesn't fit into the PCI device mod= el.
(Warning: What I write below is based on experience with= very different
architectures, both CPU and hypervisor; your mile= age may vary)

It lo= oks to me like the discussion so far is mostly focused on a "synchronous"
model where presumably the same CPU is switching context between
guest and (host) device emulation.

=
However, I/O devices on real hardware are asynchronous by construction= =2E
They do their thing while the CPU processes stuff. So at leas= t theoretically,
there is no reason to context switch on the same= CPU. You could very well
have an I/O thread on some other CPU do= ing its thing. This allows to
do something some of you may have h= eard me talk about, called
"interrupt coalescing".

As Stefan noted, this is not always a win, as it may = introduce latency.
There are at least two cases where this latenc= y really hurts:

1. When the I/O thread = is in some kind of deep sleep, e.g. because it
was not active rec= ently. Everything from cache to TLB may hit you here,
but that no= rmally happens when there isn't much I/O activity, so this case
i= n practice does not hurt that much, or rather it hurts in a case where
don't really care.

2. When the I/= O thread is preempted, or not given enough cycles to do its
stuff= =2E This happens when the system is both CPU and I/O bound, and
a= ddressing that is mostly a scheduling issue. A CPU thread could hand-off
to a specific I/O thread, reducing that case to the kind of context= switch
Alex was mentioning, but I'm not sure how feasible it is = to implement
that on Linux / kvm.

=
In such cases, you have to pay for context switch. I'm not sure if tha= t
context switch is markedly more expensive than a "vmexit". On a= t least
that alien architecture I was familiar with, there was li= ttle difference between
switching to "your" host CPU thread and s= witching to "another" host
I/O thread. But then the context switc= h was all in software, so we had
designed it that way.
=
So let's assume now that you run your device emul= ation fully in an I/O
thread, which we will assume for simplicity= sits mostly in host user-space,
and your guest I/O code runs in = a CPU thread, which we will assume
sits mostly in guest user/kern= el space.

It is possible to share two-w= ay doorbells / IRQ queues on some memory
page, very similar to a = virtqueue. When you want to "doorbell" your device,
you simply wr= ite to that page. The device threads picks it up by reading
the s= ame page, and posts I/O completions on the same page, with simple
memory writes.

Consider this I/O excha= nge buffer as having (at least) a writer and reader
index for bot= h doorbells and virtual interrupts. In the explanation
below, I w= ill call them "dwi", "dri", "iwi", "iri" for doorbell / interrupt read
and write index. (Note that as a key optimization, you really
don't want dwi and dri to be in the same cache line, since different
CPUs are going to read and write them)

You obviously still need to "kick" the I/O or CPU thread, and we are=
talking about an IPI here since you don't know which CPU that ot= her
thread is sitting on. But the interesting property is that yo= u only need
to do that when dwi=3D=3Ddri or iwi=3D=3Diri, because= if not, the other side
has already been "kicked" and will keep w= orking, i.e. incrementing
dri or iri, until it reaches back that = state.

The real "interrupt coalescing" = trick can happen here. In some
cases, you can decide to update yo= ur dwi or iwi without kicking,
as long as you know that you will = need to kick later. That requires
some heavy cooperation from gue= st drivers, though, and is a
second-order optimization.

With a scheme like this, you replace a systemati= c context switch
for each device interrupt with a memory write an= d a "fire and forget"
kick IPI that only happens when the system = is not already busy
processing I/Os, so that it can be eliminated= when the system is
most busy. With interrupt coalescing, you can= send IPIs at a rate
much lower than the actual I/O rate.

Not sure how difficult it is to adapt a scheme= like this to the current
state of qemu / kvm, but I'm pretty sur= e it works well if you implement
it correctly ;-)


A bigger issue than vmexit latency = is device emulation thread wakeup
latency. There is a thread (QEMU, vhost-user, vhost, etc) moni= toring the
ioeventfd bu= t it may be descheduled. Its physical CPU may be in a low
power state. I ran a benchmark late la= st year with QEMU's AioContext
adaptive polling disabled so we can measure the wakeup latency:

      CPU 0/KVM 26102 [000] 8562= 6.737072:       kvm:kvm_fast_mmio:
fast mmio at gpa 0xfde03000
   IO iothrea= d1 26099 [001] 85626.737076: syscalls:sys_exit_ppoll: 0x1
      &n= bsp;           4 mic= roseconds ------^

(I did not manually configure physical CP= U power states or use the
idle=3Dpoll host kernel parameter.)


Each virtqueue kick = had 4 microseconds of latency before the device
emulation thread had a chance to process the virtq= ueue. This means the
ma= ximum I/O Operations Per Second (IOPS) is capped at 250k before
virtqueue processing has even begu= n!
This data is what prompted me to write the above. Th= is 4us seems
really long to me.

I recall a benchmark where the technique above was reaching at least
400k IOPs for a single VM on a medium-size system (4CPUs (*)). =
I remember the time I ran this benchmark quite well, because it = was just
after VMware made a big splash about reaching 100k IOPs:=

(*) Yes, at the ti= me, 4 CPUs was a medium size system. Don't laugh.

=

QEMU AioContext adaptive polling helps here b= ecause we skip the vmexit
entirely while the IOThread is polling the vring (for up to 32microseconds by default).=

It would be great if more people dig into this and optimize
notifications further.

Stefan

--Apple-Mail=_91F4DD19-1EB7-4C8D-AF38-0F390772D1CD--