MMX i SSE - czy przyspieszają kopiowanie/wypełnianie
Jakiś czas temu widziałem pokazy jak przyspieszyć kopiowanie za pomocą MMX i SSE. Postanowiłem sprawdzić jak to jest. Wyniki często były zaskakujące ale wydaje mi się, że udało mi się powtórzyć je 'za każdym razem'.
Środowisko testowe: Linux notebook 2.6.29-rc3-zen1 #2 Mon Feb 9 02:27:19 CET 2009 i686 Intel(R) Celeron(R) M processor 1.50GHz GenuineIntel GNU/Linux
Ponieważ w komentarzach wykazano błędy w implementacji to poprawiłem kod.
Każdy test był wykonany na kilka sposobów:
- Standard - czyli używając memset/memcpy
- MMX - używanie MMX dopóki można. Potem standard.
- MMX[short] - używanie MMX do kopiowania po 64 bajty. Potem standard
- SSE - używanie SSE dopóki można. Potem MMX.
- SSE[short] - używanie SSE do kopiowania po 128 bajty. Potem MMX[short].
- SSE[unaligned] - używanie SSE dopóki można - używając ładowania niewyrównanego
- SSE[short,unaligned] - Połączenie SSE[short] i SSE[unaligned]
Dodatkowo przy wyrównanych testach było:
- SSE[aligned] - używanie SSE zakładając że dane są wyrównane.
- SSE[short, aligned] - Połączenie SSE[short] i SSE[aligned].
Testy były właściwie 2 rodzajów - danych wyrównanych i niewyrównanych i o znanych z góry wielkości i nieznanej z góry wielkości.
| Wbudowane | MMX | SSE | |||||||
|---|---|---|---|---|---|---|---|---|---|
| Długie | Krótkie | Długie | Krótkie | Wyrównane | Krótkie wyrównane | Niewyrównane | Krótkie niewyrównane | ||
| Wbudowane | MMX | SSE | |||||||
| Długie | Krótkie | Długie | Krótkie | Wyrównane | Krótkie wyrównane | Niewyrównane | Krótkie niewyrównane | ||
| Kopiowanie | |||||||||
| Znana z góry wielkość | |||||||||
| Wyrównane | 0.982868 | 0.731364 | 0.719066 | 0.807502 | 0.739095 | 0.782278 | 0.764494 | 0.933182 | 0.948767 |
| Niewyrównane (przesunięcie o 8) | 0.979686 | 1.01425 | 1.1122 | 0.946973 | 0.702722 | n/d | n/d | 0.80557 | 0.929962 |
| Niewyrównane (przesunięcie o 4) | 0.823551 | 0.738669 | 0.708918 | 0.731075 | 0.733556 | n/d | n/d | 0.73805 | 0.800482 |
| Niewyrównane (przesunięcie o 2) | 0.738978 | 0.998168 | 0.940978 | 0.798177 | 0.93304 | n/d | n/d | 0.790681 | 0.762154 |
| Niewyrównane (przesunięcie o 1) | 1.38732 | 0.781128 | 1.16563 | 1.54279 | 0.752582 | n/d | n/d | 0.871613 | 0.791898 |
| Nieznana z góry wielkość | |||||||||
| Wyrównane | 0.814131 | 0.762991 | 0.92082 | 0.737645 | 0.698797 | 0.744144 | 0.712088 | 0.74202 | 0.798536 |
| Niewyrównane (przesunięcie o 8) | 1.13483 | 0.83259 | 1.0434 | 0.695409 | 0.850881 | n/d | n/d | 0.862332 | 0.745039 |
| Niewyrównane (przesunięcie o 4) | 0.77926 | 0.794189 | 0.785503 | 0.822742 | 1.07177 | n/d | n/d | 0.72209 | 0.784494 |
| Niewyrównane (przesunięcie o 2) | 0.712837 | 0.829242 | 0.972949 | 0.766103 | 0.882174 | n/d | n/d | 0.88227 | 0.722228 |
| Niewyrównane (przesunięcie o 1) | 0.743727 | 0.848918 | 0.883418 | 0.794465 | 0.730376 | n/d | n/d | 0.784203 | 0.853663 |
| Wypełnianie | |||||||||
| Znana z góry wielkość | |||||||||
| Wyrównane | 0.81081 | 0.65383 | 0.695042 | 0.855161 | 0.716092 | 0.726456 | 0.705059 | 0.648848 | 0.736384 |
| Niewyrównane (przesunięcie o 8) | 0.737244 | 0.732685 | 0.652219 | 0.701332 | 0.81582 | n/d | n/d | 1.05039 | 0.674414 |
| Niewyrównane (przesunięcie o 4) | 0.968734 | 0.77509 | 0.863252 | 0.764084 | 0.868892 | n/d | n/d | 0.719646 | 0.923218 |
| Niewyrównane (przesunięcie o 2) | 0.723528 | 0.719882 | 1.06998 | 0.847719 | 0.765243 | n/d | n/d | 0.825378 | 0.824253 |
| Niewyrównane (przesunięcie o 1) | 0.71951 | 0.729501 | 0.722602 | 0.759707 | 0.609097 | n/d | n/d | 0.773491 | 0.97046 |
| Nieznana z góry wielkość | |||||||||
| Wyrównane | 0.89549 | 0.752535 | 0.697823 | 0.713467 | 0.850536 | 0.804573 | 0.893425 | 0.702687 | 0.706982 |
| Niewyrównane (przesunięcie o 8) | 0.62052 | 0.694168 | 0.840253 | 0.643335 | 0.821434 | n/d | n/d | 0.861319 | 0.788475 |
| Niewyrównane (przesunięcie o 4) | 0.747126 | 1.18094 | 0.669894 | 0.768852 | 0.849558 | n/d | n/d | 0.965763 | 0.910739 |
| Niewyrównane (przesunięcie o 2) | 0.648971 | 0.789163 | 0.639099 | 0.756582 | 0.699075 | n/d | n/d | 0.743467 | 0.71075 |
| Niewyrównane (przesunięcie o 1) | 0.655918 | 0.70555 | 0.70698 | 0.764856 | 0.772432 | n/d | n/d | 0.720736 | 0.696853 |
Albo jako przyspieszenie:
| MMX | SSE | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| Długie | Krótkie | Długie | Krótkie | Wyrównane | Krótkie wyrównane | Niewyrównane | Krótkie niewyrównane | ||
| MMX | SSE | ||||||||
| Długie | Krótkie | Długie | Krótkie | Wyrównane | Krótkie wyrównane | Niewyrównane | Krótkie niewyrównane | ||
| Kopiowanie | |||||||||
| Znana z góry wielkość | |||||||||
| Wyrównane | 25.5888 % | 26.84 % | 17.8423 % | 24.8022 % | 20.4086 % | 22.218 % | 5.05521 % | 3.46954 % | |
| Niewyrównane (przesunięcie o 8) | -3.52776 % | -13.5257 % | 3.33913 % | 28.2707 % | n/d | n/d | 17.7726 % | 5.0755 % | |
| Niewyrównane (przesunięcie o 4) | 10.3068 % | 13.9194 % | 11.2289 % | 10.9277 % | n/d | n/d | 10.382 % | 2.80116 % | |
| Niewyrównane (przesunięcie o 2) | -35.0741 % | -27.335 % | -8.01093 % | -26.2609 % | n/d | n/d | -6.99655 % | -3.13622 % | |
| Niewyrównane (przesunięcie o 1) | 43.6953 % | 15.98 % | -11.2063 % | 45.753 % | n/d | n/d | 37.1731 % | 42.919 % | |
| Nieznana z góry wielkość | |||||||||
| Wyrównane | 6.28154 % | -13.1046 % | 9.3948 % | 14.1665 % | 8.59653 % | 12.534 % | 8.85742 % | 1.91554 % | |
| Niewyrównane (przesunięcie o 8) | 26.633 % | 8.0569 % | 38.7213 % | 25.0212 % | n/d | n/d | 24.0122 % | 34.3479 % | |
| Niewyrównane (przesunięcie o 4) | -1.91579 % | -0.801145 % | -5.57991 % | -37.5374 % | n/d | n/d | 7.33645 % | -0.671663 % | |
| Niewyrównane (przesunięcie o 2) | -16.3298 % | -36.4897 % | -7.4724 % | -23.7554 % | n/d | n/d | -23.7688 % | -1.31741 % | |
| Niewyrównane (przesunięcie o 1) | -14.1438 % | -18.7826 % | -6.82213 % | 1.79515 % | n/d | n/d | -5.44232 % | -14.7818 % | |
| Wypełnianie | |||||||||
| Znana z góry wielkość | |||||||||
| Wyrównane | 19.3609 % | 14.2781 % | -5.46996 % | 11.6819 % | 10.4037 % | 13.0426 % | 19.9753 % | 9.17922 % | |
| Niewyrównane (przesunięcie o 8) | 0.618384 % | 11.5328 % | 4.87111 % | -10.6581 % | n/d | n/d | -42.4751 % | 8.52228 % | |
| Niewyrównane (przesunięcie o 4) | 19.9894 % | 10.8886 % | 21.1255 % | 10.3064 % | n/d | n/d | 25.7127 % | 4.6985 % | |
| Niewyrównane (przesunięcie o 2) | 0.50392 % | -47.8834 % | -17.1646 % | -5.7655 % | n/d | n/d | -14.0769 % | -13.9214 % | |
| Niewyrównane (przesunięcie o 1) | -1.38858 % | -0.429737 % | -5.58672 % | 15.3456 % | n/d | n/d | -7.50247 % | -34.8779 % | |
| Nieznana z góry wielkość | |||||||||
| Wyrównane | 15.9639 % | 22.0736 % | 20.3266 % | 5.02004 % | 10.1528 % | 0.2306 % | 21.5304 % | 21.0508 % | |
| Niewyrównane (przesunięcie o 8) | -11.8688 % | -35.4111 % | -3.67675 % | -32.3783 % | n/d | n/d | -38.806 % | -27.0668 % | |
| Niewyrównane (przesunięcie o 4) | -58.0641 % | 10.3372 % | -2.90794 % | -13.7101 % | n/d | n/d | -29.2637 % | -21.899 % | |
| Niewyrównane (przesunięcie o 2) | -21.6022 % | 1.52118 % | -16.5818 % | -7.72053 % | n/d | n/d | -14.5609 % | -9.51953 % | |
| Niewyrównane (przesunięcie o 1) | -7.5668 % | -7.78481 % | -16.6085 % | -17.7635 % | n/d | n/d | -9.88203 % | -6.24087 % | |
Hmm. Wydaje się, że dla 'wyrównanego' kopiowania jest to dosyć dobra metoda. Ale chętnie zobaczę jak to wygląda na Core 2 czy P4.
Kod wydaje się nie funkcjonować z -fomit-frame-pointer albo tą flagą połączoną z innymi opcjami (gcc próbuje używać rejestrów SSE?):
- #include <cstring>
- #include <iostream>
- #include <sys/time.h>
- #include <cstdlib>
- class standard
- {
- public:
- static inline void
- copy(void *dst, const void *src, size_t size) throw()
- {
- memcpy(dst, src, size);
- }
- static inline void
- fill(void *dst, char byte, size_t size) throw()
- {
- memset(dst, byte, size);
- }
- };
- union addr {
- void *ptr;
- unsigned int addr;
- };
- template<bool aligned = false, bool finish_with_self = true>
- class mmx
- {
- public:
- static inline void
- copy(void *dst, const void *src, size_t size) throw()
- {
- __asm__ __volatile__("\n"
- "\tmov %2, %%eax\n"
- "\tand $0xffffffc0, %%eax\n"
- "\tand $0x0000003f, %2\n"
- "\ttest %%eax, %%eax\n"
- "\tjz 2f\n"
- "\tmov %0, %%esi\n"
- "\tmov %1, %%edi\n"
- "\tadd %%eax, %%esi\n"
- "\tadd %%eax, %%edi\n"
- "1:\n"
- "\tmovq (%0), %%mm0\n"
- "\tmovq 8(%0), %%mm1\n"
- "\tmovq 16(%0), %%mm2\n"
- "\tmovq 24(%0), %%mm3\n"
- "\tmovq 32(%0), %%mm4\n"
- "\tmovq 40(%0), %%mm5\n"
- "\tmovq 48(%0), %%mm6\n"
- "\tmovq 56(%0), %%mm7\n"
- "\tmovq %%mm0, (%1)\n"
- "\tmovq %%mm1, 8(%1)\n"
- "\tmovq %%mm2, 16(%1)\n"
- "\tmovq %%mm3, 24(%1)\n"
- "\tmovq %%mm4, 32(%1)\n"
- "\tmovq %%mm5, 40(%1)\n"
- "\tmovq %%mm6, 48(%1)\n"
- "\tmovq %%mm7, 56(%1)\n"
- "\taddl $64, %0\n"
- "\taddl $64, %1\n"
- "\tcmp %0, %%esi\n"
- "\tjne 1b\n"
- "2:"
- : "=r"(src), "=r"(dst), "=r"(size)
- : "0"(src), "1"(dst), "2"(size)
- : "%eax", "%esi", "%edi", "memory");
- if(!aligned)
- {
- if(finish_with_self)
- {
- __asm__ __volatile__("\n"
- "\tmov %2, %%eax\n"
- "\tand $0xfffffff8, %%eax\n"
- "\tand $0x00000007, %2\n"
- "\ttest %%eax, %%eax\n"
- "\tjz 2f\n"
- "\tmov %0, %%esi\n"
- "\tmov %1, %%edi\n"
- "\tadd %%eax, %%esi\n"
- "\tadd %%eax, %%edi\n"
- "1:\n"
- "\tmovq (%0), %%mm0\n"
- "\tmovq %%mm0, (%1)\n"
- "\taddl $8, %0\n"
- "\taddl $8, %1\n"
- "\tcmp %0, %%esi\n"
- "\tjnz 1b\n"
- "2:"
- : "=r"(src), "=r"(dst), "=r"(size)
- : "0"(src), "1"(dst), "2"(size)
- : "%eax", "%esi", "%edi", "memory");
- }
- standard::copy(dst, src, size);
- }
- }
- static inline void
- fill(void *dst, char byte, size_t size) throw()
- {
- __asm__ __volatile__ ("\n"
- "\t.byte 0x0f, 0x6e, 0xc0\n"
- /* "\tmovd %1, %%mm0\n" */
- "\tpunpcklbw %%mm0, %%mm0\n"
- "\t.byte 0x0f, 0x61, 0xc0\n"
- /* "\tpunpcklwd %%mm0, %%mm0\n" */
- "\t.byte 0x0f, 0x62, 0xc0\n"
- /* "\tpunpckldq %%mm0, %%mm0\n" */
- "\tmov %2, %%esi\n"
- "\tand $0xffffffc0, %%esi\n"
- "\tand $0x0000003f, %2\n"
- "\ttest %%esi, %%esi\n"
- "\tjz 2f\n"
- "\tmov %0, %%edi\n"
- "\tadd %%esi, %%edi\n"
- "\tmovq %%mm0, %%mm1\n"
- "\tmovq %%mm0, %%mm2\n"
- "\tmovq %%mm1, %%mm3\n"
- "\tmovq %%mm2, %%mm4\n"
- "\tmovq %%mm3, %%mm5\n"
- "\tmovq %%mm4, %%mm6\n"
- "\tmovq %%mm5, %%mm7\n"
- "1:\n"
- "\tmovq %%mm0, (%0)\n"
- "\tmovq %%mm1, 8(%0)\n"
- "\tmovq %%mm2, 16(%0)\n"
- "\tmovq %%mm3, 24(%0)\n"
- "\tmovq %%mm4, 32(%0)\n"
- "\tmovq %%mm5, 40(%0)\n"
- "\tmovq %%mm6, 48(%0)\n"
- "\tmovq %%mm7, 56(%0)\n"
- "\taddl $64, %0\n"
- "\tcmpl %0, %%edi\n"
- "\tjne 1b\n"
- "2:\n"
- : "=r"(dst), "=a"(byte), "=r"(size)
- : "0"(dst), "1"(byte), "2"(size)
- : "%esi", "%edi", "memory");
- if(!aligned)
- {
- if(finish_with_self)
- {
- __asm__ __volatile__ ("\n"
- "\tmov %2, %%esi\n"
- "\tand $0xfffffff8, %%esi\n"
- "\tand $0x00000007, %2\n"
- "\ttest %%esi, %%esi\n"
- "\tjz 2f\n"
- "\tmov %0, %%edi\n"
- "\tadd %%esi, %%edi\n"
- "1:\n"
- "\tmovq %%mm0, (%0)\n"
- "\tadd $8, %0\n"
- "\tcmp %0, %%edi\n"
- "\tjne 1b\n"
- "2:\n"
- : "=r"(dst), "=r"(byte), "=r"(size)
- : "0"(dst), "1"(byte), "2"(size)
- : "%esi", "%edi", "memory");
- }
- standard::fill(dst, byte, size);
- }
- }
- };
- template<bool aligned = false, bool finish_with_self = true,
- bool fnalign = false>
- class sse
- {
- public:
- static inline void
- copy(void *_dst, const void *_src, size_t size) throw()
- {
- addr dst, src;
- dst.ptr = _dst;
- src.ptr = const_cast<void *>(_src);
- if(!fnalign)
- {
- if(dst.addr % 16 != src.addr % 16)
- {
- mmx<false, finish_with_self>::copy(dst.ptr, src.ptr, size);
- return;
- }
- if(!aligned && dst.addr % 16)
- {
- mmx<false, finish_with_self>::copy(dst.ptr, src.ptr,
- 16 - (dst.addr % 0x10));
- size -= 16 - dst.addr % 16;
- dst.addr += 16 - dst.addr % 16;
- src.addr += 16 - src.addr % 16;
- }
- __asm__ __volatile__ ("\n"
- "\tmov %2, %%eax\n"
- "\tand $0xffffff80, %%eax\n"
- "\tand $0x0000007f, %2\n"
- "\ttest %%eax, %%eax\n"
- "\tjz 2f\n"
- "\tmov %0, %%esi\n"
- "\tmov %1, %%edi\n"
- "\tadd %%eax, %%esi\n"
- "\tadd %%eax, %%edi\n"
- "1:\n"
- "\tmovaps (%0), %%xmm0\n"
- "\tmovaps 16(%0), %%xmm1\n"
- "\tmovaps 32(%0), %%xmm2\n"
- "\tmovaps 48(%0), %%xmm3\n"
- "\tmovaps 64(%0), %%xmm4\n"
- "\tmovaps 80(%0), %%xmm5\n"
- "\tmovaps 96(%0), %%xmm6\n"
- "\tmovaps 112(%0), %%xmm7\n"
- "\tmovaps %%xmm0, (%1)\n"
- "\tmovaps %%xmm1, 16(%1)\n"
- "\tmovaps %%xmm2, 32(%1)\n"
- "\tmovaps %%xmm3, 48(%1)\n"
- "\tmovaps %%xmm4, 64(%1)\n"
- "\tmovaps %%xmm5, 80(%1)\n"
- "\tmovaps %%xmm6, 96(%1)\n"
- "\tmovaps %%xmm7, 112(%1)\n"
- "\taddl $128, %0\n"
- "\taddl $128, %1\n"
- "\tcmp %0, %%esi\n"
- "\tjnz 1b\n"
- "2:"
- : "=r"(src), "=r"(dst), "=r"(size)
- : "0"(src), "1"(dst), "2"(size)
- : "%eax", "%esi", "%edi", "memory");
- if(finish_with_self)
- {
- __asm__ __volatile__("\n"
- "\tmov %2, %%eax\n"
- "\tand $0xfffffff0, %%eax\n"
- "\tand $0x0000000f, %2\n"
- "\ttest %%eax, %%eax\n"
- "\tjz 2f\n"
- "\tmov %0, %%esi\n"
- "\tmov %1, %%edi\n"
- "\tadd %%eax, %%esi\n"
- "\tadd %%eax, %%edi\n"
- "1:\n"
- "\tmovaps (%0), %%xmm0\n"
- "\tmovaps %%xmm0, (%1)\n"
- "\taddl $16, %0\n"
- "\taddl $16, %1\n"
- "\tcmp %0, %%esi\n"
- "\tjnz 1b\n"
- "2:"
- : "=r"(src), "=r"(dst), "=r"(size)
- : "0"(src), "1"(dst), "c"(size)
- : "%eax", "%esi", "%edi", "memory");
- }
- mmx<aligned, finish_with_self>::copy(dst.ptr, src.ptr, size);
- }
- else
- {
- __asm__ __volatile__ ("\n"
- "\tmov %2, %%eax\n"
- "\tand $0xffffff80, %%eax\n"
- "\tand $0x0000007f, %2\n"
- "\ttest %%eax, %%eax\n"
- "\tjz 2f\n"
- "\tmov %0, %%esi\n"
- "\tmov %1, %%edi\n"
- "\tadd %%eax, %%esi\n"
- "\tadd %%eax, %%edi\n"
- "\t1:\n"
- "\tmovups (%0), %%xmm0\n"
- "\tmovups 16(%0), %%xmm1\n"
- "\tmovups 32(%0), %%xmm2\n"
- "\tmovups 48(%0), %%xmm3\n"
- "\tmovups 64(%0), %%xmm4\n"
- "\tmovups 80(%0), %%xmm5\n"
- "\tmovups 96(%0), %%xmm6\n"
- "\tmovups 112(%0), %%xmm7\n"
- "\tmovups %%xmm0, (%1)\n"
- "\tmovups %%xmm1, 16(%1)\n"
- "\tmovups %%xmm2, 32(%1)\n"
- "\tmovups %%xmm3, 48(%1)\n"
- "\tmovups %%xmm4, 64(%1)\n"
- "\tmovups %%xmm5, 80(%1)\n"
- "\tmovups %%xmm6, 96(%1)\n"
- "\tmovups %%xmm7, 112(%1)\n"
- "\taddl $128, %0\n"
- "\taddl $128, %1\n"
- "\tcmp %0, %%esi\n"
- "\tjnz 1b\n"
- "2:"
- : "=r"(src), "=r"(dst), "=r"(size)
- : "0"(src), "1"(dst), "2"(size)
- : "%eax", "%esi", "%edi", "memory");
- if(!aligned)
- {
- if(finish_with_self)
- {
- __asm__ __volatile__("\n"
- "\tmov %2, %%eax\n"
- "\tand $0xfffffff0, %%eax\n"
- "\tand $0x0000000f, %2\n"
- "\ttest %%eax, %%eax\n"
- "\tjz 2f\n"
- "\tmov %0, %%esi\n"
- "\tmov %1, %%edi\n"
- "\tadd %%eax, %%esi\n"
- "\tadd %%eax, %%edi\n"
- "1:\n"
- "\tmovups (%0), %%xmm0\n"
- "\tmovups %%xmm0, (%1)\n"
- "\taddl $16, %0\n"
- "\taddl $16, %1\n"
- "\tcmp %0, %%esi\n"
- "\tjnz 1b\n"
- "2:"
- : "=r"(src), "=r"(dst), "=r"(size)
- : "0"(src), "1"(dst), "2"(size)
- : "%eax", "%esi", "%edi", "memory");
- }
- mmx<aligned, finish_with_self>::copy(dst.ptr, src.ptr, size);
- }
- }
- }
- static inline void
- fill(void *_dst, char byte, size_t size) throw()
- {
- if(!fnalign)
- {
- addr dst;
- dst.ptr = _dst;
- if(dst.addr % 16)
- {
- mmx<aligned, finish_with_self>::fill(dst.ptr, byte,
- 16 - dst.addr % 16);
- size -= 16 - dst.addr % 16;
- dst.addr += 16 - dst.addr % 16;
- }
- __asm__ __volatile__ ("\n"
- "\t.byte 0x66, 0x0f, 0x6e, 0xc0\n"
- /* "\tmovd %1, %%xmm0\n" */
- "\tpunpcklbw %%xmm0, %%xmm0\n"
- "\t.byte 0x66, 0x0f, 0x60, 0xc0\n"
- /* "\tpunpcklbw %%xmm0, %%xmm0\n" */
- "\t.byte 0x66, 0x0f, 0x61, 0xc0\n"
- /* "\tpunpcklwd %%xmm0, %%xmm0\n" */
- "\t.byte 0x66, 0x0f, 0x62, 0xc0\n"
- /* "\tpunpckldq %%xmm0, %%xmm0\n" */
- "\t.byte 0x0f, 0x14, 0xc0\n"
- /* "\t unpcklps %%xmm0, %%xmm0\n" */
- "\tmov %2, %%esi\n"
- "\tand $0xffffff80, %%esi\n"
- "\tand $0x0000007f, %2\n"
- "\ttest %%esi, %%esi\n"
- "\tjz 2f\n"
- "\tmov %0, %%edi\n"
- "\tadd %%esi, %%edi\n"
- "\t.byte 0x0f, 0x28, 0xc8\n"
- /* "\tmovaps %%xmm0, %%xmm1\n" */
- "\t.byte 0x0f, 0x28, 0xd0\n"
- /* "\tmovaps %%xmm0, %%xmm2\n" */
- "\t.byte 0x0f, 0x28, 0xd9\n"
- /* "\tmovaps %%xmm1, %%xmm3\n" */
- "\t.byte 0x0f, 0x28, 0xe2\n"
- /* "\tmovaps %%xmm2, %%xmm4\n" */
- "\t.byte 0x0f, 0x28, 0xeb\n"
- /* "\tmovaps %%xmm3, %%xmm5\n" */
- "\t.byte 0x0f, 0x28, 0xf4\n"
- /* "\tmovaps %%xmm4, %%xmm6\n" */
- "\t.byte 0x0f, 0x28, 0xfd\n"
- /* "\tmovaps %%xmm5, %%xmm7\n" */
- "1:\n"
- "\tmovaps %%xmm0, (%0)\n"
- "\tmovaps %%xmm1, 16(%0)\n"
- "\tmovaps %%xmm2, 32(%0)\n"
- "\tmovaps %%xmm3, 48(%0)\n"
- "\tmovaps %%xmm4, 64(%0)\n"
- "\tmovaps %%xmm5, 80(%0)\n"
- "\tmovaps %%xmm6, 96(%0)\n"
- "\tmovaps %%xmm7, 112(%0)\n"
- "\taddl $128, %0\n"
- "\tcmp %0, %%edi\n"
- "\tjnz 1b\n"
- "2:\n"
- : "=r"(dst), "=a"(byte), "=r"(size)
- : "0"(dst), "1"(byte), "2"(size)
- : "%esi", "%edi", "memory");
- if(!aligned)
- {
- if(finish_with_self)
- {
- __asm__ __volatile__ ("\n"
- "\tmov %2, %%esi\n"
- "\tand $0xfffffff8, %%esi\n"
- "\tand $0x00000007, %2\n"
- "\ttest %%esi, %%esi\n"
- "\tjz 2f\n"
- "\tmov %0, %%edi\n"
- "\tadd %%esi, %%edi\n"
- "1:\n"
- "\tmovaps %%xmm0, (%0)\n"
- "\taddl $16, %0\n"
- "\tcmp %0, %%edi\n"
- "\tjnz 1b\n"
- "2:\n"
- : "=r"(dst), "=a"(byte), "=r"(size)
- : "0"(dst), "1"(byte), "2"(size)
- : "%esi", "%edi", "memory");
- }
- mmx<aligned, finish_with_self>::fill(dst.ptr, byte, size);
- }
- }
- else
- {
- __asm__ __volatile__ ("\n"
- "\t.byte 0x66, 0x0f, 0x6e, 0xc0\n"
- /* "\tmovd %1, %%xmm0\n" */
- "\tpunpcklbw %%xmm0, %%xmm0\n"
- "\t.byte 0x66, 0x0f, 0x60, 0xc0\n"
- /* "\tpunpcklbw %%xmm0, %%xmm0\n" */
- "\t.byte 0x66, 0x0f, 0x61, 0xc0\n"
- /* "\tpunpcklwd %%xmm0, %%xmm0\n" */
- "\t.byte 0x66, 0x0f, 0x62, 0xc0\n"
- /* "\tpunpckldq %%xmm0, %%xmm0\n" */
- "\t.byte 0x0f, 0x14, 0xc0\n"
- /* "\t unpcklps %%xmm0, %%xmm0\n" */
- "\tmov %2, %%esi\n"
- "\tand $0xffffff80, %%esi\n"
- "\tand $0x0000007f, %2\n"
- "\ttest %%esi, %%esi\n"
- "\tjz 2f\n"
- "\tmov %0, %%edi\n"
- "\tadd %%esi, %%edi\n"
- "\t.byte 0x0f, 0x28, 0xc8\n"
- /* "\tmovaps %%xmm0, %%xmm1\n" */
- "\t.byte 0x0f, 0x28, 0xd0\n"
- /* "\tmovaps %%xmm0, %%xmm2\n" */
- "\t.byte 0x0f, 0x28, 0xd9\n"
- /* "\tmovaps %%xmm1, %%xmm3\n" */
- "\t.byte 0x0f, 0x28, 0xe2\n"
- /* "\tmovaps %%xmm2, %%xmm4\n" */
- "\t.byte 0x0f, 0x28, 0xeb\n"
- /* "\tmovaps %%xmm3, %%xmm5\n" */
- "\t.byte 0x0f, 0x28, 0xf4\n"
- /* "\tmovaps %%xmm4, %%xmm6\n" */
- "\t.byte 0x0f, 0x28, 0xfd\n"
- /* "\tmovaps %%xmm5, %%xmm7\n" */
- "1:\n"
- "\tmovups %%xmm0, (%0)\n"
- "\tmovups %%xmm1, 16(%0)\n"
- "\tmovups %%xmm2, 32(%0)\n"
- "\tmovups %%xmm3, 48(%0)\n"
- "\tmovups %%xmm4, 64(%0)\n"
- "\tmovups %%xmm5, 80(%0)\n"
- "\tmovups %%xmm6, 96(%0)\n"
- "\tmovups %%xmm7, 112(%0)\n"
- "\taddl $128, %0\n"
- "\tcmp %0, %%edi\n"
- "\tjnz 1b\n"
- "2:\n"
- : "=r"(_dst), "=a"(byte), "=r"(size)
- : "0"(_dst), "1"(byte), "2"(size)
- : "%esi", "%edi", "memory");
- if(!aligned)
- {
- if(finish_with_self)
- {
- __asm__ __volatile__ ("\n"
- "\tmov %2, %%esi\n"
- "\tand $0xfffffff0, %%esi\n"
- "\tand $0x0000000f, %2\n"
- "\ttest %%esi, %%esi\n"
- "\tjz 2f\n"
- "\tmov %0, %%edi\n"
- "\tadd %%esi, %%edi\n"
- "1:\n"
- "\tmovups %%xmm0, (%0)\n"
- "\taddl $16, %0\n"
- "\tcmp %0, %%edi\n"
- "\tjnz 1b\n"
- "2:\n"
- : "=r"(_dst), "=r"(byte), "=r"(size)
- : "0"(_dst), "1"(byte), "2"(size)
- : "%eax", "%esi", "%edi", "memory");
- }
- mmx<aligned, finish_with_self>::fill(_dst, byte, size);
- }
- }
- }
- };
- const size_t clearing_chunk = 16*1024*1024;
- char clearing[clearing_chunk];
- inline void
- clear_cache() throw()
- {
- for(unsigned i = 0; 0 && i < clearing_chunk; i++)
- {
- clearing[i] = rand();
- clearing[i + clearing_chunk] ^= clearing[i];
- }
- }
- inline void
- clear_data(void *_data, size_t size) throw()
- {
- union {
- void *raw;
- char *bytes;
- int *words;
- } data;
- data.raw = _data;
- size_t fast, slow;
- fast = size / sizeof(int);
- slow = size % sizeof(int);
- for(size_t i = 0; i < fast; i++)
- data.words[i] = rand();
- for(size_t i = 0; i < slow; i++)
- data.bytes[fast*sizeof(int) + i] = rand() % 256;
- }
- const unsigned n = 16;
- class test
- {
- static inline void
- check_copy(void *_ptr, void *_data, size_t size, int run)
- {
- unsigned char *ptr = reinterpret_cast<unsigned char *>(_ptr);
- unsigned char *data = reinterpret_cast<unsigned char *>(_data);
- for(size_t i = 0; i < size; i++)
- {
- if(data[i] != ptr[i])
- {
- unsigned short _p = ptr[i], _d = data[i];
- std::cerr << "Problem with " << i << " - "
- << std::hex << _p << " != "
- << std::hex << _d << " in run "
- << std::dec << run << std::endl;
- throw i;
- }
- }
- }
- static inline void
- check_fill(void *_ptr, char fill, size_t size, int run)
- {
- unsigned char *ptr = reinterpret_cast<unsigned char *>(_ptr);
- for(int i = 0; i < size; i++)
- {
- if(ptr[i] != (unsigned char)fill)
- {
- unsigned short p = ptr[i], f = ((unsigned char)fill);
- std::cerr << "Problem with " << i << " - "
- << std::hex << p << " != "
- << std::hex << f << " in run "
- << std::dec << run << std::endl;
- throw i;
- }
- }
- }
- public:
- template<typename T>
- static inline long long
- copy(void *dst, void *src, size_t size)
- {
- timeval total;
- timerclear(&total);
- for(unsigned int i = 0; i < n; i++)
- {
- timeval start, end, diff;
- clear_data(dst, size);
- clear_data(src, size);
- clear_cache();
- gettimeofday(&start, NULL);
- T::copy(dst, src, size);
- gettimeofday(&end, NULL);
- check_copy(dst, src, size, i);
- timersub(&end, &start, &diff);
- timeradd(&total, &diff, &total);
- }
- return total.tv_sec *1000000L + total.tv_usec;
- }
- template<bool aligned>
- static inline void
- copy(std::ostream &os, void *dst, void *src, size_t size)
- {
- os << "Standard: " << test::copy<standard>(dst, src, size) << "\n";
- os << "MMX: " << test::copy<mmx<false, true> >(dst, src, size) << "\n";
- os << "MMX[short]: " << test::copy<mmx<false, false> >(dst, src, size) << "\n";
- os << "SSE: " << test::copy<sse<false, true, false> >(dst, src, size) << "\n";
- os << "SSE[short]: " << test::copy<sse<false, false, false> >(dst, src, size) << "\n";
- if(aligned)
- {
- os << "SSE[aligned]: " << test::copy<sse<true, true, false> >(dst, src, size) << "\n";
- os << "SSE[short, aligned]: " << test::copy<sse<true, false, false> >(dst, src, size) << "\n";
- }
- os << "SSE[unaligned]: " << test::copy<sse<false, true, true> >(dst, src, size) << "\n";
- os << "SSE[short, unaligned]: " << test::copy<sse<false, false, true> >(dst, src, size) << "\n";
- }
- template<typename T>
- static inline long long
- fill(void *dst, char byte, size_t size)
- {
- timeval total;
- timerclear(&total);
- for(unsigned int i = 0; i < n; i++)
- {
- timeval start, end, diff;
- clear_data(dst, size);
- clear_cache();
- gettimeofday(&start, NULL);
- T::fill(dst, byte, size);
- gettimeofday(&end, NULL);
- check_fill(dst, byte, size, i);
- timersub(&end, &start, &diff);
- timeradd(&total, &diff, &total);
- }
- return total.tv_sec *1000000L + total.tv_usec;
- }
- template<bool aligned>
- static inline void
- fill(std::ostream &os, void *dst, char byte, size_t size)
- {
- os << "Standard: " << test::fill<standard>(dst, byte, size) << "\n";
- os << "MMX: " << test::fill<mmx<false, true> >(dst, byte, size) << "\n";
- os << "MMX[short]: " << test::fill<mmx<false, false> >(dst, byte, size) << "\n";
- os << "SSE: " << test::fill<sse<false, true, false> >(dst, byte, size) << "\n";
- os << "SSE[short]: " << test::fill<sse<false, false, false> >(dst, byte, size) << "\n";
- if(aligned)
- {
- os << "SSE[aligned]: " << test::fill<sse<true, true, false> >(dst, byte, size) << "\n";
- os << "SSE[short, aligned]: " << test::fill<sse<true, false, false> >(dst, byte, size) << "\n";
- }
- os << "SSE[unaligned]: " << test::fill<sse<false, true, true> >(dst, byte, size) << "\n";
- os << "SSE[short, unaligned]: " << test::fill<sse<false, false, true> >(dst, byte, size) << "\n";
- }
- };
- const size_t size = 16*1024*1024;
- char test1[size] __attribute__((aligned(16)));
- char test2[size] __attribute__((aligned(16)));
- size_t offsets[] = {8, 4, 2, 1};
- int
- main()
- {
- volatile char *vtest1 = const_cast<volatile char *>(test1);
- volatile char *vtest2 = const_cast<volatile char *>(test2);
- volatile size_t vsize = size;
- volatile size_t *voffsets = const_cast<volatile size_t *>(offsets);
- std::cout.sync_with_stdio(true);
- std::cout << "\t\tTest static, aligment copy:\n";
- test::copy<true>(std::cout, test1, test2, size);
- std::cout << "\t\tTest static, unaligment copy:\n";
- for(size_t *offset = offsets;
- offset < &offsets[sizeof(offsets)/sizeof(offsets[0])];
- offset++)
- {
- std::cout << "\tOffset: " << *offset << "\n";
- test::copy<false>(std::cout,
- test1 + *offset, test2 + *offset, size - *offset);
- }
- std::cout << "\t\tTest dynamic, aligment copy:\n";
- test::copy<true>(std::cout,
- const_cast<char *>(vtest1), const_cast<char *>(vtest2),
- vsize);
- for(volatile size_t *voffset = voffsets;
- voffset < &voffsets[sizeof(offsets)/sizeof(offsets[0])];
- voffset++)
- {
- std::cout << "\tOffset: " << *voffset << "\n";
- test::copy<false>(std::cout,
- const_cast<char *>(vtest1) + *voffset,
- const_cast<char *>(vtest2) + *voffset,
- size - *voffset);
- }
- std::cout << "\t\tTest static, aligment fill:\n";
- test::fill<true>(std::cout, test1, 0, size);
- std::cout << "\t\tTest static, unaligment fill:\n";
- for(size_t *offset = offsets;
- offset < &offsets[sizeof(offsets)/sizeof(offsets[0])];
- offset++)
- {
- std::cout << "\tOffset: " << *offset << "\n";
- test::fill<false>(std::cout, test1 + *offset, 0, size - *offset);
- }
- std::cout << "\t\tTest dynamic, aligment fill:\n";
- test::fill<true>(std::cout, const_cast<char *>(vtest1), (char)rand(), vsize);
- for(volatile size_t *voffset = voffsets;
- voffset < &voffsets[sizeof(offsets)/sizeof(offsets[0])];
- voffset++)
- {
- std::cout << "\tOffset: " << *voffset << "\n";
- test::fill<false>(std::cout, const_cast<char *>(vtest2) + *voffset,
- (char)rand(), size - *voffset);
- }
- }
Otagowano: mmx, optymalizacja, sse,
Komentarze do wpisu
Możesz śledzić odpowiedzi poprzez kanał RSS. Możesz dodać komentarz lub zostawić ślad (trackback) ze swojego bloga.
Uzytkownik
Hmm. Tabelka mieści się tylko jak zmniejszę czcionkę. Przepraszam.
14 lutego 2009, 14:26:51
sprae
A niełatwiej było użyć liboil? http://liboil.freedesktop.org/wiki/
Z resztą mój zacny kolega mIGu zwrócił uwagę na pewien aspekt. Wydajność pamięci w „Salcesonie” może być tak mała, że procesor czeka niezależnie czy jest to normalne kopiowanie, czy SSE.
14 lutego 2009, 15:02:18
Uzytkownik
„A niełatwiej było użyć liboil? http://liboil.freedesktop.org/wiki/”
Łatwiej ale nie ma zabawy ;)
„Z resztą mój zacny kolega mIGu zwrócił uwagę na pewien aspekt. Wydajność pamięci w „Salcesonie” może być tak mała, że procesor czeka niezależnie czy jest to normalne kopiowanie, czy SSE.”
Dlatego chciałbym powtórzyć na Core 2. Pewnie to dzisiaj zrobię.
14 lutego 2009, 15:05:58
Branch Predictor
W praktyce wypełnianie/kopiowanie instrukcjami nawet tylko MMX może być tak ~90% szybsze od standardowego, jedynie w Twoim kodzie poważną część zysków „kasuje” kod wejścia i wyjścia do/z nowych procedur oraz instrukcja LOOP (mogłeś ją zastąpić parą DEC/SUB + JNZ/JNS lub czymś w podobie). Pewnie sporo do powiedzenia ma także kod związany z szablonami. W programowanu na takim poziomie, nawet byle bzdura może spowodować poważne straty. Zobacz http://fastcode.sourceforge.net/ (tam poszukaj „Move Challenge” oraz „FillChar Challenge”).
14 lutego 2009, 16:18:56
Grzegorz
A ma to jakieś znaczenie dla kopiowania nie z pamięci do pamięci, tylko dla dysk – dysk lub dysk – pamięć? Bo z tego co widzę, to dysk – dysk, to w grę wchodzi tylko rozmiar bufora, np. Terra Copy.
Śledzenie [wł].14 lutego 2009, 16:23:01
Branch Predictor
@Grzegorz: .. uhm, pomylileś blogi/wpisy?
14 lutego 2009, 16:25:03
Uzytkownik
Hmm. W pliku obiektowym nie ma wejścia wyjścia z procedury gdyż jest to wyoptymalizowane (zauważ słowa typu inline) – zapomniałem podać ważnej rzeczy – czyli flag (-Os -fomit-frame-pointer -mtune=pentium-m -march=pentium-m -mfpmath=sse -pipe -w -ftree-vectorize -ftree-loop-optimize -freorder-blocks-and-partition -fgcse-sm -fgcse-las -fgcse-after-reload -ftracer -maccumulate-outgoing-args -fvisibility-inlines-hidden -fno-rtti -fno-exceptions -Wl,-O1
Wl,-add-neededWl,-as-neededWl,-hash-style=bothWl,-sort-common [Skopiowane z /etc/make.conf więc nie wszystko ma sens]).A LOOPa przeoczyłem :( Zaraz sprawdzę
@Grzegorz: Czy mi się wydaje czy Tam chodzi o kopiowanie plików? Tutaj chodzi o kopiowanie z pamięci do pamięci więc co najwyżej może mieć (i ma ogromne) znaczenie cache (którego niczym nie zmienisz – poza wyłączeniem)
14 lutego 2009, 16:28:16
Branch Predictor
Poza tym chyba masz błłąd w kodzie, w liniach 70-71: „\taddl $8, %0\n” „\taddl $8, %0\n”
nie powinno być „\taddl $8, %0\n” „\taddl $8, %1\n”
?
14 lutego 2009, 16:36:32
Uzytkownik
Nie – to jest składnia AT&T więc jest
mem src, dst
Więc:
addl $8, %0- dodaj 8 do rejestru %0zo jest równoważne w składni Intela
add eax, 8 (jeśli %0 to eax)14 lutego 2009, 16:38:57
Branch Predictor
Tak, widzę, w takim razie po co dwa razy to samo, skoro możesz raz (16 zamiast 8)? I co oraz gdzie zmienia wartość %1?
14 lutego 2009, 16:41:54
Uzytkownik
Hmm. Poczekaj. Pisałem to lekko w nocy co nie było chyba najlepszym pomysłem ;)
Zaraz dodam optymalizacje (tak wiem – powinienembył przed dodaniem wpisu).
14 lutego 2009, 16:44:05
Branch Predictor
A sprawdzałeś czy to działa prawidłowo? ;-)
14 lutego 2009, 16:45:01
Uzytkownik
W sumie nie – prawdziwi programiści nie piszą unit testów ;)
A na poważne – poszczególne fragmenty sprawdzałem ale nie całość. Tak – wiem trochę się pospieszyłem.
14 lutego 2009, 16:46:53
Branch Predictor
Hardcore :D
14 lutego 2009, 16:47:52
Branch Predictor
A tak apropos, jeśli interesujesz się optymalizowaniem na takim poziomie, to polecam książkę „Programowanie – Optymalizacja Kodu: Efektywne Wykorzystanie Pamięci” Krisa Kaspersky’ego (ISBN: 83-7243-419-0). Otwiera oczy i wyjaśnia wiele niejasności.
14 lutego 2009, 16:52:56
Uzytkownik
Co do 70 – masz rację – myślałem że masz zamiar zmienić na Intela .
14 lutego 2009, 17:42:20
Branch Predictor
Tak myślałem właśnie. ;-))
14 lutego 2009, 17:43:55
Uzytkownik
Na razie popełniłem coś takiego – choć obawiam się że mam jakiś haisenbug (wypełnia zerami – ale w gdb wszystko jest w porządku – choć jak nacisnę c to wywala). Jakby ktoś chciał rzucić okiem to prosze.
14 lutego 2009, 20:40:32
zdz
A teraz pytanie za 3 punkty, czy memcpy/memset nie mają runtime detection CPU i czy same nie korzystają z SSE przy kopiowaniu? Pytam, bo tak mi się wydaje, ale w kod glibca nie chce mi się patrzeć.
I mała uwaga, wykresiki chociażby najprostsze mile widziane.
14 lutego 2009, 22:18:31
Uzytkownik
Nie – z tego co widzialem nie. A na pewno built-in nie ma.
14 lutego 2009, 22:19:47
darkjames
memcpy() zaimplementowany w SSE do kernelspace’a,
dostajemy zmmapowany entrypoint w vdso.
i wszystkie programy dostaja 100% speedupa!
(oczywiscie po rekompilacji, i zaimplementowaniu tego w libcu.)
14 lutego 2009, 22:40:02
Uzytkownik
W kernelspace? Nie opaca sie. Syscall to ok. 2000 cykli (nie pamitam czy w jedna czy dwie strony) – a nie dostajemy nic w zamian (bo kod w userspace i kernelspace wykonuje sie z podobna szybkoscia).
14 lutego 2009, 22:42:38
darkjames
nic o syscallach nie mowilem;
mowilem tylko zeby kernel wybral runtime najszybsza wersje memcpy() nawet moze robic benchmarki przy startowaniu. a potem dal procesowi dostep do niej.
14 lutego 2009, 22:55:27
Dodaj komentarz