Dlaczego pisanie do pamięci jest dużo wolniejsze niż czytanie?
Oto prosty memset
benchmark przepustowości:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main()
{
unsigned long n, r, i;
unsigned char *p;
clock_t c0, c1;
double elapsed;
n = 1000 * 1000 * 1000; /* GB */
r = 100; /* repeat */
p = calloc(n, 1);
c0 = clock();
for(i = 0; i < r; ++i) {
memset(p, (int)i, n);
printf("%4d/%4ld\r", p[0], r); /* "use" the result */
fflush(stdout);
}
c1 = clock();
elapsed = (c1 - c0) / (double)CLOCKS_PER_SEC;
printf("Bandwidth = %6.3f GB/s (Giga = 10^9)\n", (double)n * r / elapsed / 1e9);
free(p);
}
W moim systemie (szczegóły poniżej) z pojedynczym modułem pamięci DDR3-1600, wyprowadza:
Jest to 37% teoretycznej prędkości pamięci RAM:Przepustowość = 4.751 GB / s (Giga = 10^9)
1.6 GHz * 8 bytes = 12.8 GB/s
Z drugiej strony, oto podobny test" Czytaj":
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
unsigned long do_xor(const unsigned long* p, unsigned long n)
{
unsigned long i, x = 0;
for(i = 0; i < n; ++i)
x ^= p[i];
return x;
}
int main()
{
unsigned long n, r, i;
unsigned long *p;
clock_t c0, c1;
double elapsed;
n = 1000 * 1000 * 1000; /* GB */
r = 100; /* repeat */
p = calloc(n/sizeof(unsigned long), sizeof(unsigned long));
c0 = clock();
for(i = 0; i < r; ++i) {
p[0] = do_xor(p, n / sizeof(unsigned long)); /* "use" the result */
printf("%4ld/%4ld\r", i, r);
fflush(stdout);
}
c1 = clock();
elapsed = (c1 - c0) / (double)CLOCKS_PER_SEC;
printf("Bandwidth = %6.3f GB/s (Giga = 10^9)\n", (double)n * r / elapsed / 1e9);
free(p);
}
Wychodzi:
Przepustowość = 11.516 GB/s (Giga = 10^9)
Mogę zbliżyć się do teoretyczne ograniczenie wydajności odczytu, takie jak XORing dużej tablicy, ale pisanie wydaje się być znacznie wolniejsze. Dlaczego?
OS Ubuntu 14.04 AMD64 (kompiluję z gcc -O3
. Użycie {[6] } sprawia, że wydajność odczytu jest nieco gorsza, ale nie wpływa na memset
)
CPU Xeon E5-2630 v2
RAM pojedynczy "16GB PC3-12800 Parity REG CL11 240-Pin DIMM" (co jest napisane na pudełku) myślę, że posiadanie pojedynczego DIMM sprawia, że wydajność jest bardziej przewidywalna. I ' m zakładając, że przy 4 Dimmach, {[3] } będzie do 4 razy szybciej.
Płyta Główna Supermicro X9DRG-QF (obsługuje 4-kanałową pamięć)
System dodatkowy: laptop z 2X 4GB PAMIĘCI RAM DDR3-1067: odczyt i zapis to około 5,5 GB / s, ale należy pamiętać, że używa 2 modułów DIMM.
P. S. zastąpienie memset
tą wersją skutkuje dokładnie taką samą wydajnością
void *my_memset(void *s, int c, size_t n)
{
unsigned long i = 0;
for(i = 0; i < n; ++i)
((char*)s)[i] = (char)c;
return s;
}
7 answers
Z Twoimi programami, dostaję
(write) Bandwidth = 6.076 GB/s
(read) Bandwidth = 10.916 GB/s
Na komputerze stacjonarnym (Core i7, x86-64, GCC 4.9, GNU libc 2.19) z sześcioma modułami DIMM 2GB. (Nie mam więcej szczegółów niż to, przepraszam.)
Jednak ten program zgłasza zapis pasma 12.209 GB/s
:
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <emmintrin.h>
static void
nt_memset(char *buf, unsigned char val, size_t n)
{
/* this will only work with aligned address and size */
assert((uintptr_t)buf % sizeof(__m128i) == 0);
assert(n % sizeof(__m128i) == 0);
__m128i xval = _mm_set_epi8(val, val, val, val,
val, val, val, val,
val, val, val, val,
val, val, val, val);
for (__m128i *p = (__m128i*)buf; p < (__m128i*)(buf + n); p++)
_mm_stream_si128(p, xval);
_mm_sfence();
}
/* same main() as your write test, except calling nt_memset instead of memset */
Magia jest w _mm_stream_si128
, aka Instrukcja maszynowa movntdq
, która zapisuje 16-bajtową ilość do pamięci RAM systemu, omijając pamięć podręczną (oficjalnym żargonem na to jest " non-temporal store"). Myślę, że to dość jednoznacznie pokazuje, że różnica wydajności jest Wszystko o zachowaniu pamięci podręcznej.
Uwaga: glibc 2.19 posiada rozbudowaną, ręcznie zoptymalizowaną instrukcję memset
wykorzystującą instrukcje wektorowe. Jednak nie używa Nie-czasowych magazynów. Prawdopodobnie jest to właściwe dla memset
; ogólnie rzecz biorąc, wyczyścisz pamięć krótko przed jej użyciem, więc chcesz, aby była gorąca w pamięci podręcznej. (Przypuszczam, że nawet cleverer memset
może przełączyć się na sklepy nie-temporalne dla naprawdę ogromnego bloku clear, na teorii, że nie możesz chcieć tego wszystkiego w pamięci podręcznej, ponieważ pamięć podręczna po prostu nie jest taka duża.)
Dump of assembler code for function memset:
=> 0x00007ffff7ab9420 <+0>: movd %esi,%xmm8
0x00007ffff7ab9425 <+5>: mov %rdi,%rax
0x00007ffff7ab9428 <+8>: punpcklbw %xmm8,%xmm8
0x00007ffff7ab942d <+13>: punpcklwd %xmm8,%xmm8
0x00007ffff7ab9432 <+18>: pshufd $0x0,%xmm8,%xmm8
0x00007ffff7ab9438 <+24>: cmp $0x40,%rdx
0x00007ffff7ab943c <+28>: ja 0x7ffff7ab9470 <memset+80>
0x00007ffff7ab943e <+30>: cmp $0x10,%rdx
0x00007ffff7ab9442 <+34>: jbe 0x7ffff7ab94e2 <memset+194>
0x00007ffff7ab9448 <+40>: cmp $0x20,%rdx
0x00007ffff7ab944c <+44>: movdqu %xmm8,(%rdi)
0x00007ffff7ab9451 <+49>: movdqu %xmm8,-0x10(%rdi,%rdx,1)
0x00007ffff7ab9458 <+56>: ja 0x7ffff7ab9460 <memset+64>
0x00007ffff7ab945a <+58>: repz retq
0x00007ffff7ab945c <+60>: nopl 0x0(%rax)
0x00007ffff7ab9460 <+64>: movdqu %xmm8,0x10(%rdi)
0x00007ffff7ab9466 <+70>: movdqu %xmm8,-0x20(%rdi,%rdx,1)
0x00007ffff7ab946d <+77>: retq
0x00007ffff7ab946e <+78>: xchg %ax,%ax
0x00007ffff7ab9470 <+80>: lea 0x40(%rdi),%rcx
0x00007ffff7ab9474 <+84>: movdqu %xmm8,(%rdi)
0x00007ffff7ab9479 <+89>: and $0xffffffffffffffc0,%rcx
0x00007ffff7ab947d <+93>: movdqu %xmm8,-0x10(%rdi,%rdx,1)
0x00007ffff7ab9484 <+100>: movdqu %xmm8,0x10(%rdi)
0x00007ffff7ab948a <+106>: movdqu %xmm8,-0x20(%rdi,%rdx,1)
0x00007ffff7ab9491 <+113>: movdqu %xmm8,0x20(%rdi)
0x00007ffff7ab9497 <+119>: movdqu %xmm8,-0x30(%rdi,%rdx,1)
0x00007ffff7ab949e <+126>: movdqu %xmm8,0x30(%rdi)
0x00007ffff7ab94a4 <+132>: movdqu %xmm8,-0x40(%rdi,%rdx,1)
0x00007ffff7ab94ab <+139>: add %rdi,%rdx
0x00007ffff7ab94ae <+142>: and $0xffffffffffffffc0,%rdx
0x00007ffff7ab94b2 <+146>: cmp %rdx,%rcx
0x00007ffff7ab94b5 <+149>: je 0x7ffff7ab945a <memset+58>
0x00007ffff7ab94b7 <+151>: nopw 0x0(%rax,%rax,1)
0x00007ffff7ab94c0 <+160>: movdqa %xmm8,(%rcx)
0x00007ffff7ab94c5 <+165>: movdqa %xmm8,0x10(%rcx)
0x00007ffff7ab94cb <+171>: movdqa %xmm8,0x20(%rcx)
0x00007ffff7ab94d1 <+177>: movdqa %xmm8,0x30(%rcx)
0x00007ffff7ab94d7 <+183>: add $0x40,%rcx
0x00007ffff7ab94db <+187>: cmp %rcx,%rdx
0x00007ffff7ab94de <+190>: jne 0x7ffff7ab94c0 <memset+160>
0x00007ffff7ab94e0 <+192>: repz retq
0x00007ffff7ab94e2 <+194>: movq %xmm8,%rcx
0x00007ffff7ab94e7 <+199>: test $0x18,%dl
0x00007ffff7ab94ea <+202>: jne 0x7ffff7ab950e <memset+238>
0x00007ffff7ab94ec <+204>: test $0x4,%dl
0x00007ffff7ab94ef <+207>: jne 0x7ffff7ab9507 <memset+231>
0x00007ffff7ab94f1 <+209>: test $0x1,%dl
0x00007ffff7ab94f4 <+212>: je 0x7ffff7ab94f8 <memset+216>
0x00007ffff7ab94f6 <+214>: mov %cl,(%rdi)
0x00007ffff7ab94f8 <+216>: test $0x2,%dl
0x00007ffff7ab94fb <+219>: je 0x7ffff7ab945a <memset+58>
0x00007ffff7ab9501 <+225>: mov %cx,-0x2(%rax,%rdx,1)
0x00007ffff7ab9506 <+230>: retq
0x00007ffff7ab9507 <+231>: mov %ecx,(%rdi)
0x00007ffff7ab9509 <+233>: mov %ecx,-0x4(%rdi,%rdx,1)
0x00007ffff7ab950d <+237>: retq
0x00007ffff7ab950e <+238>: mov %rcx,(%rdi)
0x00007ffff7ab9511 <+241>: mov %rcx,-0x8(%rdi,%rdx,1)
0x00007ffff7ab9516 <+246>: retq
(to jest w libc.so.6
, a nie sam program -- druga osoba, która próbowała zrzucić assembly dla memset
wydaje się, że tylko znalazła swój wpis PLT. Najprostszym sposobem na uzyskanie zrzutu montażowego dla prawdziwego memset
w systemie Unixy jest
$ gdb ./a.out
(gdb) set env LD_BIND_NOW t
(gdb) b main
Breakpoint 1 at [address]
(gdb) r
Breakpoint 1, [address] in main ()
(gdb) disas memset
...
.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-14 16:00:55
Główna różnica w wydajności wynika z zasad buforowania twojego komputera / regionu pamięci. Gdy odczytywane są z pamięci, A danych nie ma w pamięci podręcznej, pamięć musi być najpierw pobierana do pamięci podręcznej za pomocą magistrali pamięci, zanim będzie można wykonać jakiekolwiek obliczenia z danymi. Jednak podczas zapisu do pamięci istnieją różne zasady zapisu. Najprawdopodobniej Twój system używa pamięci podręcznej do zapisu (a dokładniej "write allocate"), co oznacza, że podczas zapisu w Miejscu Pamięci tego nie ma w pamięci podręcznej, dane są najpierw pobierane z pamięci podręcznej do pamięci podręcznej, a ostatecznie zapisywane z powrotem do pamięci, gdy dane są eksmitowane z pamięci podręcznej, co oznacza round-trip dla danych i 2x wykorzystanie przepustowości magistrali przy zapisie. Istnieje również zasada buforowania zapisu (lub "No-write allocate"), co ogólnie oznacza, że po zapisie bufora-miss at dane nie są pobierane do bufora, a co powinno dać taką samą wydajność zarówno dla odczytu, jak i zapisu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-14 01:35:22
Różnica - przynajmniej na moim komputerze, z procesorem AMD-polega na tym, że program read używa wektorowych operacji. Dekompilacja tych dwóch daje to dla programu do pisania:
0000000000400610 <main>:
...
400628: e8 73 ff ff ff callq 4005a0 <clock@plt>
40062d: 49 89 c4 mov %rax,%r12
400630: 89 de mov %ebx,%esi
400632: ba 00 ca 9a 3b mov $0x3b9aca00,%edx
400637: 48 89 ef mov %rbp,%rdi
40063a: e8 71 ff ff ff callq 4005b0 <memset@plt>
40063f: 0f b6 55 00 movzbl 0x0(%rbp),%edx
400643: b9 64 00 00 00 mov $0x64,%ecx
400648: be 34 08 40 00 mov $0x400834,%esi
40064d: bf 01 00 00 00 mov $0x1,%edi
400652: 31 c0 xor %eax,%eax
400654: 48 83 c3 01 add $0x1,%rbx
400658: e8 a3 ff ff ff callq 400600 <__printf_chk@plt>
Ale to dla programu do czytania:
00000000004005d0 <main>:
....
400609: e8 62 ff ff ff callq 400570 <clock@plt>
40060e: 49 d1 ee shr %r14
400611: 48 89 44 24 18 mov %rax,0x18(%rsp)
400616: 4b 8d 04 e7 lea (%r15,%r12,8),%rax
40061a: 4b 8d 1c 36 lea (%r14,%r14,1),%rbx
40061e: 48 89 44 24 10 mov %rax,0x10(%rsp)
400623: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
400628: 4d 85 e4 test %r12,%r12
40062b: 0f 84 df 00 00 00 je 400710 <main+0x140>
400631: 49 8b 17 mov (%r15),%rdx
400634: bf 01 00 00 00 mov $0x1,%edi
400639: 48 8b 74 24 10 mov 0x10(%rsp),%rsi
40063e: 66 0f ef c0 pxor %xmm0,%xmm0
400642: 31 c9 xor %ecx,%ecx
400644: 0f 1f 40 00 nopl 0x0(%rax)
400648: 48 83 c1 01 add $0x1,%rcx
40064c: 66 0f ef 06 pxor (%rsi),%xmm0
400650: 48 83 c6 10 add $0x10,%rsi
400654: 49 39 ce cmp %rcx,%r14
400657: 77 ef ja 400648 <main+0x78>
400659: 66 0f 6f d0 movdqa %xmm0,%xmm2 ;!!!! vectorized magic
40065d: 48 01 df add %rbx,%rdi
400660: 66 0f 73 da 08 psrldq $0x8,%xmm2
400665: 66 0f ef c2 pxor %xmm2,%xmm0
400669: 66 0f 7f 04 24 movdqa %xmm0,(%rsp)
40066e: 48 8b 04 24 mov (%rsp),%rax
400672: 48 31 d0 xor %rdx,%rax
400675: 48 39 dd cmp %rbx,%rbp
400678: 74 04 je 40067e <main+0xae>
40067a: 49 33 04 ff xor (%r15,%rdi,8),%rax
40067e: 4c 89 ea mov %r13,%rdx
400681: 49 89 07 mov %rax,(%r15)
400684: b9 64 00 00 00 mov $0x64,%ecx
400689: be 04 0a 40 00 mov $0x400a04,%esi
400695: e8 26 ff ff ff callq 4005c0 <__printf_chk@plt>
40068e: bf 01 00 00 00 mov $0x1,%edi
400693: 31 c0 xor %eax,%eax
Zauważ również, że Twój "domowy" memset
jest właściwie zoptymalizowany do wywołania memset
:
00000000004007b0 <my_memset>:
4007b0: 48 85 d2 test %rdx,%rdx
4007b3: 74 1b je 4007d0 <my_memset+0x20>
4007b5: 48 83 ec 08 sub $0x8,%rsp
4007b9: 40 0f be f6 movsbl %sil,%esi
4007bd: e8 ee fd ff ff callq 4005b0 <memset@plt>
4007c2: 48 83 c4 08 add $0x8,%rsp
4007c6: c3 retq
4007c7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
4007ce: 00 00
4007d0: 48 89 f8 mov %rdi,%rax
4007d3: c3 retq
4007d4: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
4007db: 00 00 00
4007de: 66 90 xchg %ax,%ax
Nie mogę znaleźć żadnych odniesień dotyczących tego, czy memset
używa wektoryzowanych operacji, demontaż memset@plt
jest nieprzydatny tutaj:
00000000004005b0 <memset@plt>:
4005b0: ff 25 72 0a 20 00 jmpq *0x200a72(%rip) # 601028 <_GLOBAL_OFFSET_TABLE_+0x28>
4005b6: 68 02 00 00 00 pushq $0x2
4005bb: e9 c0 ff ff ff jmpq 400580 <_init+0x20>
To pytanie sugeruje, że ponieważ memset
jest zaprojektowany do obsługi każdej sprawy, może zabraknąć pewnych optymalizacji.
Ten facet zdecydowanie wydaje się przekonany, że aby skorzystać z instrukcji SIMD, trzeba wykonać własny asembler memset
. to pytanie również .
Zrobię zdjęcie w ciemności i domyślam się, że nie używa operacji SIMD, ponieważ nie może stwierdzić, czy będzie działać, czy nie na czymś, co jest wielokrotnością wielkości jednej wektoryzowanej operacji, albo Jest jakiś problem związany z wyrównaniem.
Możemy jednak potwierdzić, że jest to , a nie problem wydajności pamięci podręcznej, sprawdzając za pomocą cachegrind
. Program write tworzy:
==19593== D refs: 6,312,618,768 (80,386 rd + 6,312,538,382 wr)
==19593== D1 misses: 1,578,132,439 ( 5,350 rd + 1,578,127,089 wr)
==19593== LLd misses: 1,578,131,849 ( 4,806 rd + 1,578,127,043 wr)
==19593== D1 miss rate: 24.9% ( 6.6% + 24.9% )
==19593== LLd miss rate: 24.9% ( 5.9% + 24.9% )
==19593==
==19593== LL refs: 1,578,133,467 ( 6,378 rd + 1,578,127,089 wr)
==19593== LL misses: 1,578,132,871 ( 5,828 rd + 1,578,127,043 wr) <<
==19593== LL miss rate: 9.0% ( 0.0% + 24.9% )
A program read produkuje:
==19682== D refs: 6,312,618,618 (6,250,080,336 rd + 62,538,282 wr)
==19682== D1 misses: 1,578,132,331 (1,562,505,046 rd + 15,627,285 wr)
==19682== LLd misses: 1,578,131,740 (1,562,504,500 rd + 15,627,240 wr)
==19682== D1 miss rate: 24.9% ( 24.9% + 24.9% )
==19682== LLd miss rate: 24.9% ( 24.9% + 24.9% )
==19682==
==19682== LL refs: 1,578,133,357 (1,562,506,072 rd + 15,627,285 wr)
==19682== LL misses: 1,578,132,760 (1,562,505,520 rd + 15,627,240 wr) <<
==19682== LL miss rate: 4.1% ( 4.1% + 24.9% )
Podczas gdy program read ma niższą liczbę błędów LL, ponieważ wykonuje o wiele więcej odczytów( dodatkowy odczyt na operację XOR
), całkowita liczba błędów wynosi to samo. Więc cokolwiek jest problemem, nie ma go tam.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:08:44
Buforowanie i lokalizacja prawie na pewno wyjaśniają większość efektów, które widzisz.
Nie ma buforowania ani lokalizacji w zapisach, chyba że chcesz systemu niedeterministycznego. Większość czasów zapisu jest mierzona jako czas potrzebny na dane, aby dostać się do nośnika pamięci (niezależnie od tego, czy jest to dysk twardy, czy układ pamięci), podczas gdy odczyty mogą pochodzić z dowolnej liczby warstw pamięci podręcznej, które są szybsze niż nośnik pamięci.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-13 23:45:11
Może być tak, jak działa (System-jako-całość). Szybkość odczytu wydaje się być powszechnym trendem z szerokim zakresem względnej wydajności przepustowej. Na szybka analiza DDR3 Intel i wykresy DDR2 wymienione, jako kilka wybranych przypadków (zapis/odczyt)%;
Niektóre wydajne układy DDR3 zapisują około ~60-70% przepustowości odczytu. Istnieją jednak moduły pamięci (np. Golden Empire CL11-13-13 D3-2666) ~ 30% pisz
Wydajne układy DDR2 wydają się mieć tylko około ~50% przepustowości zapisu w porównaniu do odczytu. Ale są też szczególnie źli pretendenci (tj. OCZ OCZ21066NEW_BT1G) do ~20%.
Chociaż może to nie wyjaśniać przyczyny zapisu/odczytu ~40%, ponieważ użyty kod benchmarka i konfiguracja są prawdopodobnie różne (notatki są niejasne), jest to zdecydowanie A czynnik. (Chciałbym uruchomić kilka istniejących programów benchmarkowych i sprawdzić, czy liczby są zgodne z kodami zamieszczonymi w pytaniu.)
Aktualizacja:
Pobrałem tabelę wyszukiwania pamięci z połączonej strony i przetworzyłem ją w Excelu. Chociaż nadal pokazuje szeroki zakres wartości, jest znacznie mniej przecinany niż oryginalna odpowiedź powyżej, która spojrzała tylko na chipy pamięci odczytane z góry i kilka wybranych "interesujących" wpisów z Wykresów. Nie jestem pewien, po co te rozbieżności, zwłaszcza w strasznych pretendentach wymienionych powyżej, nie występują na liście drugorzędnej.
Jednak, nawet pod nowymi liczbami różnica nadal waha się od 50% -100% (mediana 65, średnia 65) wydajności odczytu. Należy pamiętać, że tylko dlatego, że chip był" 100% " sprawny w stosunku zapisu/odczytu nie oznacza, że był lepszy ogólnie .. tylko, że była bardziej wyrównana między obiema operacjami.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-14 23:32:58
Oto moja hipoteza robocza. Jeśli jest poprawny, wyjaśnia, dlaczego pisze się dwa razy wolniej niż czyta:
Mimo że memset
zapisuje tylko do pamięci wirtualnej, ignorując jej poprzednią zawartość, na poziomie sprzętowym komputer nie może wykonać czystego zapisu do pamięci DRAM: odczytuje zawartość pamięci DRAM do pamięci podręcznej, modyfikuje je tam, a następnie zapisuje z powrotem do pamięci DRAM. Dlatego, na poziomie sprzętowym, memset
robi zarówno czytanie, jak i pisanie (chociaż pierwsze wydaje się bezużyteczne)! Stąd mniej więcej Dwukrotna różnica prędkości.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-14 00:48:02
Ponieważ aby odczytać, wystarczy pulsować linie adresowe i odczytywać Stany rdzenia na liniach zmysłów. Cykl zapisu następuje po dostarczeniu danych do procesora, a tym samym nie spowalnia procesu. Z drugiej strony, aby pisać, musisz najpierw wykonać fałszywy odczyt, aby zresetować rdzenie, a następnie wykonać cykl zapisu.
(Na wszelki wypadek, gdyby nie było to oczywiste, ta odpowiedź brzmi "język w policzku" - opisując, dlaczego zapis jest wolniejszy niż odczyt na Starym pudełku pamięci rdzenia.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-15 11:39:45