From nobody Thu May 2 00:22:33 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=gmail.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1623935204506592.467710823129; Thu, 17 Jun 2021 06:06:44 -0700 (PDT) Received: from localhost ([::1]:39388 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1ltrjH-00041w-BO for importer@patchew.org; Thu, 17 Jun 2021 09:06:43 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33378) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ltqxq-0003Xt-TE for qemu-devel@nongnu.org; Thu, 17 Jun 2021 08:17:44 -0400 Received: from mail-wr1-x42c.google.com ([2a00:1450:4864:20::42c]:43976) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1ltqxj-0007gi-Ik for qemu-devel@nongnu.org; Thu, 17 Jun 2021 08:17:41 -0400 Received: by mail-wr1-x42c.google.com with SMTP id r9so6526749wrz.10 for ; Thu, 17 Jun 2021 05:17:33 -0700 (PDT) Received: from localhost.localdomain ([197.61.161.108]) by smtp.gmail.com with ESMTPSA id g17sm6992721wrh.72.2021.06.17.05.17.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 17 Jun 2021 05:17:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=dAr9g0AieygoclZAfwvNBjVDVWhGKveWbQwqjqjvajk=; b=raYv8VhTgbx15x+yjpqz3CMs8fdcTIbL7zrPm1chNTzInCyaUUC7joTwvn0OgD/yYh JiOdOkJRsRizWZa+/z1tql2UnkjgWVyClbSUTzX4QejKzOAgYtJeUvcEwciV05P6HRoX eRm72s3w/GFwwlgy1cnLq+3VfgsKaFV0cxqPYCKKyvDz+8DboQieHLOp6OuIesj3AcnJ JTrIwbJ13Il2Dml4mFiYhh/sHAbRyxo1bY5fsySArrcRTv3uUrqUcb+SB8MiaaBHKbVR tlgON6OMB6oO/+IpjWBPPxoZ/jhdE3kQeSuAl+P10P0DtsXFbmwBMGlL0Rh3Pcpf23x0 N3ug== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=dAr9g0AieygoclZAfwvNBjVDVWhGKveWbQwqjqjvajk=; b=ptoZkPqYIbohxR0zBB5Uoh89VhkuUBmy+KV8WYbg+evA3K8Vwvg3pwR9IrA05mtdZ4 0/SQxP8NCJY7WTDUFIXXsROyp2gawUVNLjfLQCfzoCqbEghE7WM651kMzPplaW3EdzhA Aa39sCrSY0WX55wUrWdM7SlbGr8DYJTcQZRVb/hkSpJrJR8g9cwQwEc+k2b9faVQ+QUT wPm6/rs9F8bMtNroxpEtZ5/g/17ryOKD+gu0cCrOCFwl+N51nL+Bqk08aY67zruNh21O ml5RXRX/DICrwzlpDdWBbdxU/4sFOwtHblkdxpzJ6zYKNVDWIqI2RbamCRJP/Cfcxuvd Aeeg== X-Gm-Message-State: AOAM531CVbNXvb2prKZqjvvCBE4sLHMNuzIrycvnUzKtOwT3ACK2smv1 g4GAGz0BR9CFRjxg7FrrYkaymiR2gHC3lg== X-Google-Smtp-Source: ABdhPJy+j3cRf1QwHHciFY6kTVCEUD49rlouP/DYnnpnLWvAZeZpoWrhvu3DmlJCiAJcYkjB6Qbo3A== X-Received: by 2002:a05:6000:1847:: with SMTP id c7mr5365254wri.368.1623932251108; Thu, 17 Jun 2021 05:17:31 -0700 (PDT) From: Mahmoud Mandour To: qemu-devel@nongnu.org Subject: [RFC PATCH v2] Add a post for the new TCG cache modelling plugin Date: Thu, 17 Jun 2021 14:17:07 +0200 Message-Id: <20210617121707.764126-1-ma.mandourr@gmail.com> X-Mailer: git-send-email 2.25.1 MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::42c; envelope-from=ma.mandourr@gmail.com; helo=mail-wr1-x42c.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: stefanha@gmail.com, Mahmoud Mandour , alex.bennee@linaro.org Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) This post introduces the new TCG plugin `cache` that's used for cache modelling. This plugin is a part of my GSoC 2021 participation. Signed-off-by: Mahmoud Mandour --- v1 -> v2: Elaborated some more in the introduction and broken up some sentences. Changed the example of blocked matrix multiplication to a simpler solution that requires less explanation since matrix multiplication itself is out of the scope of the post. Added the code for the `mm` function but did not directly give away the cache-problematic portion and preferred to "investigate" the problem since it's the job of the plugin to help knowing where the problem is. Added an epilogue in which I summarize the work and the patches posted until now.=20 .../2021-06-17-tcg-cache-modelling-plugin.md | 266 ++++++++++++++++++ screenshots/2021-06-17-cache-structure.png | Bin 0 -> 19675 bytes 2 files changed, 266 insertions(+) create mode 100644 _posts/2021-06-17-tcg-cache-modelling-plugin.md create mode 100644 screenshots/2021-06-17-cache-structure.png diff --git a/_posts/2021-06-17-tcg-cache-modelling-plugin.md b/_posts/2021-= 06-17-tcg-cache-modelling-plugin.md new file mode 100644 index 0000000..5360916 --- /dev/null +++ b/_posts/2021-06-17-tcg-cache-modelling-plugin.md @@ -0,0 +1,266 @@ +--- +layout: post +title: "Cache Modelling TCG Plugin" +date: 2021-06-17 06:00:00 +0200 +author: Mahmoud Mandour +categories: [TCG plugins, GSOC] +--- + +TCG plugins provide means to instrument generated code for both user-mode = and +full system emulation. This includes the ability to intercept every memory +access and instruction execution. This post introduces a new TCG plugin th= at's +used to simulate configurable L1 separate instruction cache and data cache. + +The plugin aims to simply model different configurations of icache and dca= che +rather than simulate intricate microarchitectural details. That's because = those +details generally vary from one microarchitecture to another. Also, those +details will be very different between TCG and real behavior. L1 caches are +usually of low associativity, so simulating them will give us a good idea = of +what happens in real life with code that thrashes the cache. + +## Overview + +The plugin simulates how L1 user-configured caches would behave when given= a +working set defined by a program in user-mode, or system-wide working set. +Subsequently, it logs performance statistics along with the most N +cache-thrashing instructions. + +### Configurability + +The plugin is configurable in terms of: + +* icache size parameters: `arg=3D"I=3DCACHE_SIZE ASSOC BLOCK_SIZE"` +* dcache size parameters: `arg=3D"D=3DCACHE_SIZE ASSOC BLOCK_SIZE"` +* Eviction policy: `arg=3D"evict=3Dlru|rand|fifo"` +* How many top-most thrashing instructions to log: `arg=3D"limit=3DTOP_N"` +* How many core caches to keep track of: `arg=3D"cores=3DN_CORES"` + +### Multicore caching + +Multicore caching is achieved by having independent L1 caches for each ava= ilable +core. + +In __full-system emulation__, the number of available vCPUs is known to the +plugin at plugin installation time, so separate caches are maintained for = those. + +In __user-space emulation__, the index of the vCPU initiating a memory acc= ess +monotonically increases and is limited with however much the kernel allows +creating. The approach used is that we allocate a static number of caches,= and +fit all memory accesses into those cores. This is viable having more threa= ds +than cores will result in interleaving those threads between the available= cores +so they might thrash each other anyway. + +## Design and implementation + +### General structure + +A generic cache data structure, `struct Cache`, is used to model either an +icache or dcache. For each known core, the plugin maintains an icache and a +dcache. On a memory access coming from a core, the corresponding cache is +interrogated. + +Each cache has a number of cache sets that are used to store the actual ca= ched +locations alongside metadata that backs eviction algorithms. The structure= of a +cache with `n` sets, and `m` blocks per sets is summarized in the following +figure: + +![cache structure](/screenshots/2021-06-17-cache-structure.png) + +### Eviction algorithms + +The plugin supports three eviction algorithms: + +* Random eviction +* Least recently used (LRU) +* FIFO eviction + +#### Random eviction + +On a cache miss that requires eviction, a randomly chosen block is evicted= to +make room for the newly-fetched block. + +Using random eviction effectively requires no metadata for each set. + +#### Least recently used (LRU) + +For each set, a generation number is maintained that is incremented on each +memory access and. The current generation number is assigned to the block +currently being accessed. On a cache miss, the block with the least genera= tion +number is evicted. + +#### FIFO eviction + +A FIFO queue instance is maintained for each set. On a cache miss, the evi= cted +block is the first-in block, and the newly-fetched block is enqueued as the +last-in block. + +## Usage + +Now a simple example usage of the plugin is demonstrated by running a prog= ram +that has does matrix multiplication, and how the plugin helps identify cod= e that +thrashes the cache. + +A program, `test_mm` uses the following function to carry out matrix +multiplication: + +``` +void mm(int n, int m1[n][n], int m2[n][n], int res[n][n]) +{ + for (int i =3D 0; i < n; i++) { + for (int j =3D 0; j < n; j++) { + int sum =3D 0; + for (int k =3D 0; k < n; k++) { + int op1 =3D m1[i][k]; + int op2 =3D m2[k][j]; + sum +=3D op1 * op2; + } + res[i][j] =3D sum; + } + } +} +``` + +Running `mm_test` inside QEMU using the following command: + +``` +./x86_64-linux-user/qemu-x86_64 $(QEMU_ARGS) \ + -plugin ./contrib/plugins/libcache.so,arg=3D"D=3D8192 4 64",arg=3D"I=3D8= 192 4 64" \ + -d plugin \ + -D matmul.log \ + ./mm_test +``` + +The preceding command will run QEMU and attach the plugin with the followi= ng +configuration: + +* dcache: cache size =3D 8KBs, associativity =3D 4, block size =3D 64B. +* icache: cache size =3D 8KBs, associativity =3D 4, block size =3D 64B. +* Default eviction policy is LRU (used for both caches). +* Default number of cores is 1. + +The following data is logged in `matmul.log`: + +``` +core #, data accesses, data misses, dmiss rate, insn accesses, insn misses= , imiss rate +0 4908419 274545 5.5933% 8002457 1005 = 0.0126% + +address, data misses, instruction +0x4000001244 (mm), 262138, movl (%rdi, %rsi, 4), %esi +0x400000121c (mm), 5258, movl (%rdi, %rsi, 4), %esi +0x4000001286 (mm), 4096, movl %edi, (%r8, %rsi, 4) +0x400000199c (main), 257, movl %edx, (%rax, %rcx, 4) + +... +``` + +We can observe two things from the logs: + +* The most cache-thrashing instructions belong to a symbol called `mm`, wh= ich + happens to be the matrix multiplication function. +* Some array-indexing instructions are generating the greatest share of da= ta + misses. + +`test_mm` does a bunch of other operations other than matrix multiplicatio= n. +However, Using the plugin data, we can narrow our investigation space to `= mm`, +which happens to be generating about 98% of the overall number of misses. + +Now we need to find out why is the instruction at address `0x4000001224` +thrashing the cache. Looking at the disassembly of the program, using +`objdump -Sl test_mm`: + +``` +/path/to/test_mm.c:11 (discriminator 3) + int op2 =3D m2[k][j]; <- The line of code we're intereste= d in + 1202: 8b 75 c0 mov -0x40(%rbp),%esi + 1205: 48 63 fe movslq %esi,%rdi + 1208: 48 63 f2 movslq %edx,%rsi + 120b: 48 0f af f7 imul %rdi,%rsi + 120f: 48 8d 3c b5 00 00 00 lea 0x0(,%rsi,4),%rdi + 1216: 00 + 1217: 48 8b 75 a8 mov -0x58(%rbp),%rsi + 121b: 48 01 f7 add %rsi,%rdi + 121e: 8b 75 c8 mov -0x38(%rbp),%esi + 1221: 48 63 f6 movslq %esi,%rsi + 1224: 8b 34 b7 mov (%rdi,%rsi,4),%esi + ^^^^^^^^^^^^^^^^^^^^^^^^^ + 1227: 89 75 d4 mov %esi,-0x2c(%rbp) +``` + +It can be seen that the most problematic instruction is associated with lo= ading +`m2[k][j]`. This happens because we're traversing `m2` in a column-wise or= der. +So if the matrix `m2` is larger than the data cache, we end up with fetchi= ng +blocks that we only use one integer from and not use again before getting +evicted. + +A simple solution to this problem is to [transpose](https://en.wikipedia.o= rg/wiki/Transpose) +the second matrix and access it in a row-wise order. + +By editing the program to transpose `m2` before calling `mm` and run it in= side +QEMU with the plugin attached and using the same configuration as previous= ly, +the following data is logged in `matmul.log`: + +``` +core #, data accesses, data misses, dmiss rate, insn accesses, insn misses= , imiss rate +0 4998994 24235 0.4848% 8191937 1009 = 0.0123% + +address, data misses, instruction +0x4000001244 (mm), 16447, movl (%rdi, %rsi, 4), %esi +0x4000001359 (tran), 3994, movl (%rcx, %rdx, 4), %ecx +0x4000001aa7 (main), 257, movl %edx, (%rax, %rcx, 4) +0x4000001a72 (main), 257, movl %ecx, (%rax, %rdx, 4) + +... +``` + +It can be seen that a minor number of misses is generated at transposition= time +in `tran`. The rest of the matrix multiplication is carried out using the = same +procedure but to multiply `m1[i][k]` by `m2[j][k]`. So `m2` is traversed +row-wise and hence utilized cache space much more optimally. + +### Multi-core caching + +The plugin accepts a `cores=3DN_CORES` argument that represents the number= of +cores that the plugin must keep track of. Memory accesses generated by exc= ess +threads will be served through the available core caches. + +An example usage of the plugin using the `cores` argument against a progra= m that +creates 4 threads: + +``` +./x86_64-linux-user/qemu-x86_64 $(QEMU_ARGS) \ + -plugin ./contrib/plugins/libcache.so,arg=3D"cores=3D4" \ + -d plugin \ + -D logfile \ + ./threaded_prog +``` + +This reports out the following: + +``` +core #, data accesses, data misses, dmiss rate, insn accesses, insn misses= , imiss rate +0 76739 4195 5.411666% 242616 1555 = 0.6409% +1 29029 932 3.211106% 70939 988 = 1.3927% +2 6218 285 4.511835% 15702 382 = 2.4328% +3 6608 297 4.411946% 16342 384 = 2.3498% +sum 118594 5709 4.811139% 345599 3309 = 0.9575% + +... +``` + +## Conclusion + +By emulating simple configurations of icache and dcache we can gain insigh= ts +about how a working set is utilizing cache memory. Simplicity is sought and +L1 cache is emphasized since its under-utilization can be severe to the ov= erall +system performance. + +This plugin is made as part of my GSoC participation for the year 2021 und= er the +mentorship of Alex Benn=C3=A9e. + +List of posted patches related to the plugin: + +[[RFC PATCH v3 0/4] Cache TCG plugin & symbol-resolution API](https://patc= hew.org/QEMU/20210608040532.56449-1-ma.mandourr@gmail.com/) =20 +[[RFC PATCH v3 1/4] plugins/api: expose symbol lookup to plugins](https://= patchew.org/QEMU/20210608040532.56449-1-ma.mandourr@gmail.com/2021060804053= 2.56449-2-ma.mandourr@gmail.com/) =20 +[[RFC PATCH v3 2/4] plugins: Added a new cache modelling plugin.](https://= patchew.org/QEMU/20210608040532.56449-1-ma.mandourr@gmail.com/2021060804053= 2.56449-3-ma.mandourr@gmail.com/) =20 +[[RFC PATCH v3 3/4] plugins/cache: Enabled cache parameterization](https:/= /patchew.org/QEMU/20210608040532.56449-1-ma.mandourr@gmail.com/202106080405= 32.56449-4-ma.mandourr@gmail.com/) =20 +[[RFC PATCH v3 4/4] plugins/cache: Added FIFO and LRU eviction policies](h= ttps://patchew.org/QEMU/20210608040532.56449-1-ma.mandourr@gmail.com/202106= 08040532.56449-5-ma.mandourr@gmail.com/) diff --git a/screenshots/2021-06-17-cache-structure.png b/screenshots/2021-= 06-17-cache-structure.png new file mode 100644 index 0000000000000000000000000000000000000000..82a9dc07710e3d9be738d20c675= c69aa90a48942 GIT binary patch literal 19675 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 literal 0 HcmV?d00001 --=20 2.25.1