shared memory: Какая атомарность гарантируется
Там есть флаги для mmap и shm, если ты хочешь семафоры делать или ещё что-нибудь.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."
mmap от shmat отличается? Я вторую использую, но думал, что это просто разные стандарты, приводящие к одному и тому же
см. первые два параграфа из документа про барьеры:
http://www.kernel.org/doc/Documentation/memory-barriers.txt
Вопрос 1:Если адрес выровнен, то чтение / запись 64-битного значения будут атомарными.
Предположим, один процесс пишет по адресу *p (изначально там a0) данные а1, a2...
А второе по этому же адресу читает. Можно ли гарантировать, будет прочитано a0 или a1 (а не половина а0, а вторая половина от a1)?
Вопрос 2(пусть вопрос 1 решился положительно):Может так получиться, что процесс 1 уже всё записал и завершился, а второй ещё читать и не начинал.
Один процесс пишет по адресу *pa и *pb данные по такому алгоритму.
1. изначально в *pa a0, в *pb b0; i=0
2 i+=1
3 в *pa пишется ai
4 в *pb пишется bi
5 переходим к п.2
Второй процесс читает, к примеру, B = *pb, потом A = *pa. (т.е. в обратном порядке)
Может ли так получиться (из-за какой-нибудь оптимизации процессоров и памяти что
A = a1
B = b2 ?
Предположим, один процесс пишет по адресу *p (изначально там a0) данные а1, a2...атомарность, конечно же, будет на уровне машинного слова
А второе по этому же адресу читает. Можно ли гарантировать, будет прочитано a0 или a1 (а не половина а0, а вторая половина от a1)?
процессор не может прочитать полслова в регистр, переключиться на другой поток и забить регистр другой половиной
Если адрес выровнен, то чтение / запись 64-битного значения будут атомарными.Да, если речь идет об ассемблере.
В C/C++ нет такой гарантии, даже для volatile, и нужно использовать атомики.
А где-нибудь в одном месте собрана эта мудрость?
на 2 вопрос ответ утвердительный
платформа x86_64У x86_64 реордеринг только вида "Stores reordered after loads" wiki. То есть CPU ничего "оптимизировать" не будет.
Получается, что A=a1, B=b2 может быть только благодаря компилятору, нет?
Что-то есть здесь: http://www.hpl.hp.com/techreports/2008/HPL-2008-56.pdf
Можно новый стандарт С++ читать.
Дреппера, кстати, можно читать. Но там довольно много.
атомарность, конечно же, будет на уровне машинного словаДа, но процессоров может быть несколько, а слово может быть порвано на две кэш-линии. Как происходит синхронизация этих линий в x86_64, я не знаю. Годные линки приветствуются
процессор не может прочитать полслова в регистр, переключиться на другой поток и забить регистр другой половиной
Если адрес выровнен, то чтение / запись 64-битного значения будут атомарными.разве не надо еще добавлять инструкцию, чтобы кэши процессора синхронизировались с памятью?
Software Developer’s Manual,
Volume 3A: System Programming Guide, Part 1
http://www.intel.com/Assets/ja_JP/PDF/manual/253668.pdf (не обращайте внимание на слово ja_JP - на самом деле PDF на английском)
8.1.1 Guaranteed Atomic Operations
8.1.2 Bus Locking
разве не надо еще добавлять инструкцию, чтобы кэши процессора синхронизировались с памятью?надо
запись надо делать locked (т.е. добавлять "lock" перед xchg, cmpxchg и прочими)
тогда любое чтение будет консистентно
64-битное слово помещается в кэш-линию целиком, поэтому и синхронизироваться по любому cache-coherence протоколу должно без особых проблем
Вам устное замечание. Распространение заведомо неверной информации. утверждение (про атомарность) неверно для случая, когда используется многопроцессорная машина
Нет, это Вам устное замечание.
Запись будет _атомарной_ - никто и никогда не увидит ее выполненной частично. Вы путаете атомарность с memory ordering.
разве не надо еще добавлять инструкцию, чтобы кэши процессора синхронизировались с памятью?Когерентный кэш невидим для программы (кроме таймингов, конечно). Т.е. можно считать что любой значение которое ушло из процессора в его кэш напрямую попадает в память.
разве не надо еще добавлять инструкцию, чтобы кэши процессора синхронизировались с памятью?Атомарность доступа к памяти и когерентность кэшей - это разные вещи.
То, что чтение / запись выровненного 64-битного слова атомарны означает, что невозможно такое, чтобы в общей 64-битной области памяти получилась часть байт из одной такой операции, а часть из другой. Независимо от того, на каких процессорах и в какие моменты они были выполнены, и в каких кэшах они успели побывать.
64-битное слово помещается в кэш-линию целиком
Если адрес этого слова выровнен — да, помещается.
Если нет — легко можно разложить в две линии: например, по адресу cacheline_size-1.
просто в своей практике не встречался с тем, чтобы компилятор не выравнивал переменные, посему и туплю
Атомарность доступа к памяти и когерентность кэшей - это разные вещи.был не прав, действительно их попутал
в п. 8.2.3.2 как раз ответ на второй вопрос есть с примером. (и тут вики уже правильно процитировали)
Processor 0
mov [ _x], 1
mov [ _y], 1
Processor 1
mov r1, [ _y]
mov r2, [ _x]
Initially x == y == 0
r1 == 1 and r2 == 0 is not allowed
можно считать что любой значение которое ушло из процессора в его кэш напрямую попадает в память.
А вот тут в примере допускается, что кэш одного места памят может быть в разных кэшах(вернее, это такая реализация может быть)
Example 8-5. Intra-Processor Forwarding is Allowed
Processor 0
mov [ _x], 1
mov r1, [ _x]
mov r2, [ _y]
Processor 1
mov [ _y], 1
mov r3, [ _y]
mov r4, [ _x]
Initially x == y == 0
r2 == 0 and r4 == 0 is allowed
Т.е. получается без какой-то явной синхронизации не обойтись. И судя по http://www.kernel.org/doc/Documentation/memory-barriers.txt все эти семафоры в том числе делают memory barrier.
А я хотел избавиться от семафоров как раз, чтобы быстрее данные можно было прочитать.
хотя вот тут http://stackoverflow.com/questions/11282899/memory-barrier-a... пишут, что
или я как-то не так понял последний пример?
The memory system must guarantee cache coherence.
Это пример о том, что x86 процессоры видят события в памяти в одном и том же порядке, с одним исключением - результат своей записи процессор всегда видит немедленно, даже если запись еще на дошла до памяти.
Опиши подробнее что требуется, кто такие ai и bi. Может ли процесс-получатель пропускать некоторые пары?
читатели делают, грубо говоря,
for(;;)
{
value = *pa;
usleep(10);
}
хотелось бы, чтобы *pa было максимально возможно новое.
обновление самих pa просходят относительно редко (в 1000 раз реже чтений)
В C/C++ нет такой гарантии, даже для volatile, и нужно использовать атомики.А что, на практике действительно возможны варианты/компиляторы, когда запись/чтение 64-битного выровненного слова будет неатомарной на x86_64?
struct
{
int16 a;
int64_t a1;
int64_t a2;
int64_t a3;
int64_t a4;
int64_t a5;
} __attribute__packed
А это в свою очередь имеет смысл делать, когда в файл пишешь какие-нибудь бинарные структуры или читаешь
Сдается мне, что ты пропустил слово "выровненного" в моей фразе.
А что, на практике действительно возможны варианты/компиляторы, когда запись/чтение 64-битного выровненного слова будет неатомарной на x86_64?подозреваю, что возможны: http://stackoverflow.com/questions/5258627/atomic-64-bit-wri...
я пока склоняюсь к тому, что на наших любимых процах x86 и наших любимых компиляторах VS + gcc атомарным будет чтение\запись
1) выровненных 32 битных значений, если скомпилировано под x32
2) выровненных 32 и 64 битных значений, если скомпилировано под x64
http://msdn.microsoft.com/en-us/libRary/ms684122%28v=VS.85%2...
добавлю ссылочку на мсдн: Simple reads and writes to properly-aligned 32-bit variables are atomic operations.
......
Simple reads and writes to properly aligned 64-bit variables are atomic on 64-bit Windows. Reads and writes to 64-bit values are not guaranteed to be atomic on 32-bit Windows. Reads and writes to variables of other sizes are not guaranteed to be atomic on any platform.
На x86_64 нет инструкции для записи длинного immediate значения в память. Это делается либо через временный регистр, либо как две 4-байтных записи.
volatile long long y;
volatile int a, b, c, d, e, f, g, h;
volatile int a1, b1, c1, d1, e1, f1, g1, h1;
void ff {
int a_ = a;
int b_ = b;
int c_ = c;
int d_ = d;
int e_ = e;
int f_ = f;
int g_ = g;
int h_ = h;
int a1_ = a1;
int b1_ = b1;
int c1_ = c1;
int d1_ = d1;
int e1_ = e1;
int f1_ = f1;
int g1_ = g1;
// int h1_ = h1;
y = 42213213213213LL;
a = a_;
b = b_;
c = c_;
d = d_;
e = e_;
f = f_;
g = g_;
h = h_;
a1 = a1_;
b1 = b1_;
c1 = c1_;
d1 = d1_;
e1 = e1_;
f1 = f1_;
g1 = g1_;
// h1 = h1_;
}
# g++ -O3 -c -S -fno-omit-frame-pointer
...
movabsq $42213213213213, %rax
movq %rax, y(%rip)
...
Если раскомментировать две строки, gcc решит что пора экономить регистры:
...
movl $-2020339171, y(%rip)
movl $9828, y+4(%rip)
...
upd: опередили
Т.е. если я пишу в один байт, это не превращается в чтение слова, изменения его сдвигами и записи опять целого слова? Т.е. если два потока пишут в разные байты одного слова, могут ли они влиять друг на друга?
afaik, превращается, т.е. именно выбирается целое слово, часть его используется/модифицируется, потом возвращается на место. про синхронизацию при возвращении на место не знаю.
Компилятор иногда может сделать такую оптимизацию. Иногда не может - например, если эти байты относятся к разным location, то добавление чтения-записи соседних байтов там где этого раньше не было может внести data race и undefined behavior.
И еще есть false sharing, но это только производительность.
PS
Сейчас добавил в пример выше строки
char *pc = reinterpret_cast<char*>(a)+1;
(*pc) = 123;
и оно расвернулось в
> movslq a(%rip %rax
> movb $123, 1(%rax)
т.е. одна инструкция на изменение байта, т.к. в регистры ничего не выгрузилось. Может я пример плохой сделал конечно.
Т.е. компилятор может сгенерировать неатомарную запись 1 байта, если он уверен что другие потоки этого не заметят. Про другие процессы он ничего знает, и через shared memory эту подставу можно увидеть. Ну и в ассемблерном листинге, конечно.
Т.е. получается, что единственное, что можно читать писать это atomic_t (судя по обсуждению, это 32bit, кстати, так и не смог найти что нужно заинклудить, чтобы этот тип доступен был) Даже с atomic_t/2 может получиться та же история, что и с байтами.
Опять же. А где гаранития, что эти 32-bit уйдут одной инструкцией в память, и не получится, что два процессора оба решат сделать
movq для этого, например в таком контексте:
int64_t data[4];
int32_t *p = reinterpret_cast<int32_t*>(&data);
for(int i=0;i<8;i++)
{
*(p+i) = i*i;
}
компилятор возьми да разверни цикл и записывать будет сразу 64 бита.
PS; в интернетах ссылаются на x86_64-linux-gnu/asm/atomic.h, но у меня такого файла нет
Может ли так получиться что в результате записи одного байта попортятся соседние байты?
Поток 1 делает mem[0x100001] = 23
Поток 2 делает mem[0x100000] = 17, которое реализуется как зачитывание 4 байтов с 0x100000 по 0x100003, изменение первого, записывание всех четырёх обратно. В результате изменение сделанное первым потоком может потеряться, хотя логически они доступались к разным адресам и не должны были конфликтовать.
Так может получиться? Даже в двух частях: может ли подставу устроить железо и может ли подставу устроить компилятор?
Т.е. компилятор может сгенерировать неатомарную запись 1 байта
Это пример о том, что x86 процессоры видят события в памяти в одном и том же порядке, с одним исключением - результат своей записи процессор всегда видит немедленно, даже если запись еще на дошла до памяти.
а сколько максимум времени может работать один тред на одном одноядерном intel процессоре до того момента, как его прервёт таскменеджер? если это настраивается, то как называется параметр?
то есть 2 треда: A и B, которые не конкурируют ни за какой рессурс кроме процессора, будут во времени выполняться как-то так:
T0 ... T1: A
T1 ... T2: taskmanager
T2 ... T3: B
T3 ... T4: taskmanager
T4 ... T5: A
T5 ... T6: taskmanager
.....
вопрос - чему равно T2k+1 - T2k? в виндовс это ~1мс
PS: я уже два года хочу об этом узнать.
PS: я уже два года хочу об этом узнать.За два года не смог открыть гугль и написать что-нибудь типа "linux scheduler quantum"?
И сдаётся мне, инфа про 1 мс в виндоус неверна примерно на порядок.
это время называется timeslice или quantum
в виндовс это ~1мсна windows-е гранулярность scheduler-а - 1мс, но время timeslice-а для тредов с нормальным приоритетом - 10мс для клиентской системы, и 100мс для серверной
http://doc.opensuse.org/documentation/html/openSUSE/opensuse...
при этом
sysctl:
kernel.sched_min_granularity_ns = 2250000
kernel.sched_latency_ns = 18000000
Если я правильно всё понял, то получается:
sched_latency_ns/sched_min_granularity_ns = 8 > 2, поэтому каждому будет даваться по 18ms/2.
А если запустить 100 задач, то каждой будет по 2.25ms даваться.
тут пишут, что gran = ( lat / rtasks ) - ( lat / rtasks / rtasks )
при этом
sysctl:
kernel.sched_min_granularity_ns = 2250000
kernel.sched_latency_ns = 18000000
Если я правильно всё понял, то получается:
sched_latency_ns/sched_min_granularity_ns = 8 > 2, поэтому каждому будет даваться по 18ms/2.
А если запустить 100 задач, то каждой будет по 2.25ms даваться.
кроме таймслайсов ещё важно где может произойти переключение контекста: в обычных ядрах текущая задача вытесняется только если она в юзерспейсе, а в preemptive — в любом неатомарном ядерном контексте. когда допишут full dyntick периодический таймер исчезнет вообще.
см. первые два параграфа из документа про барьеры:ага правильный источник знаний. ссылаться на на утверждения из C или тем более С++ странно, их писали не для этого. то что существуют архитектуры где сложение работает только с насыщением а чтение двойного слова не атомарно относительно прерывания конечно прекрасно, но кого это волнует..
http://www.kernel.org/doc/Documentation/memory-barriers.txt
данные ходят кэшлайнами так что одна инструкция чтения/записи не может перемешать байты в слове,
и если где-то это не так то я бы хотел посмотреть на автора и как он там будет реализовывать хоть какую-то многозадачность или не дай боже smp.
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git...
Properly aligned pointers, longs, ints, and chars (and unsigned
equivalents) may be atomically loaded from and stored to in the same
sense as described for atomic_read and atomic_set. The ACCESS_ONCE
macro should be used to prevent the compiler from using optimizations
that might otherwise optimize accesses out of existence on the one hand,
or that might create unsolicited accesses on the other.
For example consider the following code:
while (a > 0)
do_something;
If the compiler can prove that do_something does not store to the
variable a, then the compiler is within its rights transforming this to
the following:
tmp = a;
if (a > 0)
for (;;)
do_something;
If you don't want the compiler to do this (and you probably don't then
you should use something like the following:
while (ACCESS_ONCE(a) < 0)
do_something;
Alternatively, you could place a barrier call in the loop.
For another example, consider the following code:
tmp_a = a;
do_something_with(tmp_a);
do_something_else_with(tmp_a);
If the compiler can prove that do_something_with does not store to the
variable a, then the compiler is within its rights to manufacture an
additional load as follows:
tmp_a = a;
do_something_with(tmp_a);
tmp_a = a;
do_something_else_with(tmp_a);
This could fatally confuse your code if it expected the same value
to be passed to do_something_with and do_something_else_with.
The compiler would be likely to manufacture this additional load if
do_something_with was an inline function that made very heavy use
of registers: reloading from variable a could save a flush to the
stack and later reload. To prevent the compiler from attacking your
code in this manner, write the following:
tmp_a = ACCESS_ONCE(a);
do_something_with(tmp_a);
do_something_else_with(tmp_a);
For a final example, consider the following code, assuming that the
variable a is set at boot time before the second CPU is brought online
and never changed later, so that memory barriers are not needed:
if (a)
b = 9;
else
b = 42;
The compiler is within its rights to manufacture an additional store
by transforming the above code into the following:
b = 42;
if (a)
b = 9;
This could come as a fatal surprise to other code running concurrently
that expected b to never have the value 42 if a was zero. To prevent
the compiler from doing this, write something like:
if (a)
ACCESS_ONCE(b) = 9;
else
ACCESS_ONCE(b) = 42;
Don't even -think- about doing this without proper use of memory barriers,
locks, or atomic operations if variable a can change at runtime!
/*
* Prevent the compiler from merging or refetching accesses. The compiler
* is also forbidden from reordering successive instances of ACCESS_ONCE
* but only when the compiler is aware of some particular ordering. One way
* to make the compiler aware of ordering is to put the two invocations of
* ACCESS_ONCE in different C statements.
*
* This macro does absolutely -nothing- to prevent the CPU from reordering,
* merging, or refetching absolutely anything at any time. Its main intended
* use is to mediate communication between process-level code and irq/NMI
* handlers, all running on the same CPU.
*/
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x)
full dyntick
Можно в двух словах что это? и как оно исчезнуть может вообще? Я что-то ничего понятного не могу нагуглить.
Average: CPU 0/s 3/s 4/s 8/s 9/s 16/s 18/s 19/s 21/s 64/s 65/s 66/s 67/s 68/s 69/s 70/s 71/s 72/s 73/s 74/s 75/s 76/s 77/s 83/s 84/s 85/s 86/s 87/s 88/s 89/s 90/s 91/s NMI/s LOC/s SPU/s PMI/s IWI/s RES/s CAL/s TLB/s TRM/s THR/s MCE/s MCP/s ERR/s MIS/s
Average: 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 76.00 0.00 408.00 0.00 63.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 32.00 0.00 0.00 0.00 37.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 9.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 29.00 0.00 0.00 0.00 25.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 2.00 0.00 0.00 0.00 3.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 4 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 50.00 0.00 0.00 0.00 24.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 5 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 2.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 6 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 26.00 0.00 0.00 0.00 33.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 7 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 8 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 25.00 0.00 0.00 0.00 27.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 9 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 10 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 19.00 0.00 0.00 0.00 14.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 11 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 12 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 13 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 209.00 0.00 0.00 0.00 1556.00 28.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 14 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 16.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 15 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 20.00 0.00 0.00 0.00 178.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 16 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 4.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 17 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 175.00 0.00 0.00 0.00 858.00 7.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 18 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 19 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 15.00 0.00 0.00 0.00 108.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 20 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 0.00 0.00 0.00 4.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 21 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 133.00 0.00 0.00 0.00 1206.00 30.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 22 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 2.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: 23 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 104.00 0.00 0.00 0.00 937.00 15.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
ну фактически он one-shot но тикает не реже HZ раз в секунду если процессор не в idle-state т.к. постоянно есть периодическое событие.
данные ходят кэшлайнами так что одна инструкция чтения/записи не может перемешать байты в слове,Я правильно понимаю, ты хочешь сказать что в
и если где-то это не так то я бы хотел посмотреть на автора и как он там будет реализовывать хоть какую-то многозадачность или не дай боже smp.
volatile unsigned long long x;
x = 0x1111111111111111;
из другого треда/процесса невозможно увидеть половину байтов нулей, а половину единиц? И дело в том что x целиком лежит в одной строке кэша?
выше сказано только про лонги. лонг лонг на 32-битных машинах не всегда можно записать и прочитать одной инструкцией. на и386 есть какой-то хак (лень смотреть где и какой)
Атомарно или нет?
но слово должно быть выровнено.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
volatile unsigned long y;
volatile int a, b, c, d, e, f, g, h;
volatile int a1, b1, c1, d1, e1, f1, g1, h1;
volatile int a2, b2, c2, d2;
void ff {
int a_ = a;
int b_ = b;
int c_ = c;
int d_ = d;
int e_ = e;
int f_ = f;
int g_ = g;
int h_ = h;
int a1_ = a1;
int b1_ = b1;
int c1_ = c1;
int d1_ = d1;
int e1_ = e1;
int f1_ = f1;
int g1_ = g1;
int h1_ = h1;
int a2_ = a2;
int b2_ = b2;
int c2_ = c2;
int d2_ = d2;
for (int i = 0; i < 1000000000; ++i) {
y = 0x100000002;
y = 0x300000004;
}
a = a_;
b = b_;
c = c_;
d = d_;
e = e_;
f = f_;
g = g_;
h = h_;
a1 = a1_;
b1 = b1_;
c1 = c1_;
d1 = d1_;
e1 = e1_;
f1 = f1_;
g1 = g1_;
h1 = h1_;
a2 = a2_;
b2 = b2_;
c2 = c2_;
d2 = d2_;
}
void* thread_func1(void* p) {
ff;
}
void* thread_func2(void* p) {
while (1) {
unsigned long z = y;
if (z != 0 && z != 0x100000002 && z != 0x300000004) {
printf("%lx\n", z);
exit(1);
}
}
}
int main(int argc, char**argv) {
pthread_t t[2];
pthread_create(&t[0], 0, thread_func1, 0);
pthread_create(&t[1], 0, thread_func2, 0);
pthread_join(t[0], 0);
pthread_join(t[1], 0);
return 0;
}
$ g++ -O3 2.cc -o 2 -lpthread
$ g++ --version
g++ (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ ./2
100000004
Update: исправил пример чтобы работало с -O3
Чувак, на этом утверждении держится всё RCU в ядре. Мне конечно интересно где ты облажался, но не сейчас.
Похоже, этот код демонстрирует баги в компиляторе. Мало того, что записи в volatile бьются на две, так ещё и (на 4.6.3 последующие записи в a, b, ... h1 удаляются, типа, как избыточные. , есть желание зафайлить баги на gcc.gnu.org? Если нет, я на днях займусь.
Записи он удаляет совершенно справедливо - это unreachable code.
Щас исправлю пример, чтобы он переживал -O3.
Идиотом не назовут, разделение volatile write'ов на два это серьёзная проблема quality of implementation, даже если формально допускается буквой стандарта.
Cможешь объяснить как в таком случает работает RCU в ядре? rcu_assign_pointer не содержит ни одного volatile. Или я не там ищу?
Мало того, что записи в volatile бьются на двену и прекрасно. он что какие-то короткие мувы решил использовать раз половинки мелкие?
если он так-же NULL половинками будет присваивать то наступит суровая жопа.
Речь о том, что, кажется, исправление volatile в gcc ему не поможет.
Он решил использовать короткие мувы, потому что immediate длинный, а register pressure высокий. Для NULL это не актуально.
Т.е. уже понятно, что он там на честном слове держится.ага, нужно чинить и гсс и ядро.
Речь о том, что, кажется, исправление volatile в gcc ему не поможет.
уже третий баг найденный в этом трэде — первый что документация на кернел.орг не обновлялась с 3.0
Он решил использовать короткие мувы, потому что immediate длинный, а register pressure высокий. Для NULL это не актуально.а то что тут цикл это для него не повод отжать регистры?
Но вообще цикл это не очень серьёзный повод в данном случае, потому что альтернатива — использовать 0 регистров.
данные ходят кэшлайнами так что одна инструкция чтения/записи не может перемешать байты в словеМожешь пояснять про это? Что ты имел ввиду под перемешиванием? Я сбился с тренда.
< while(true)
---
> for (int i = 0; i < 10000000; i++)
И g++ -O[23] фейлит (g++ v4.7.2). Круто, чо =)
Upd: icc v13.0.1 не фейлит.
Да, я именно так сделал (обновил пост и теперь gcc 4.7.2 с любыми флагами разбивает стор на 2.
> с нормальным приоритетом - 10мс для клиентской системы, и 100мс для серверной
если запустить 2 треда, которые конкурируют _только_ за проц, я на 99.9% (из практики) уверен, что они будут выполнться по 1мс, а не по 10мс или 100мс. собственно вопрос - а что тогда такое слайс и когда он хоть как-то проявляется?
Щас исправлю пример, чтобы он переживал -O3Я так понимаю, что пример ты уже поправил? Потому что я полчаса уже ломаю голову, с чего там unreachable-code и почему у меня в asm'е он не выкидывается
> kernel.sched_min_granularity_ns = 2250000
> kernel.sched_latency_ns = 18000000
> Если я правильно всё понял, то получается:
> sched_latency_ns/sched_min_granularity_ns = 8 > 2, поэтому каждому будет даваться по 18ms/2.
> А если запустить 100 задач, то каждой будет по 2.25ms даваться.
а кто-нибудь пробовал поменять и замерить? а то я про винду тоже нагугливал и про 10мс и про 100мс, а на деле оказалась 1мс ...
с линуксами к сожалению из-за того, что их 100500 видов я не смог выбрать какой установить, чтобы проверить
ага, там был бесконечный цикл для надежности
$ diff atomic6.cpp atomic5.cc
5,8c5
< #include <atomic>
<
< std::atomic<unsigned long> y;
< //volatile unsigned long y;
---
> volatile unsigned long y;
$ g++ -O3 -std=c++11 atomic6.cpp -lpthread -o mem6
$ ./mem6
100000004
как поправить?
Это уже явно бага. Он делает все то же самое что для volatile, но добавляет mfence
Кстати, вот как этот цикл выглядит для std::atomic<long long> на i386.
.L8:
movl %ecx, 72(%esp)
movl %ebx, 76(%esp)
fildq 72(%esp)
fistpq y
lock orl $0, (%esp)
movl %eax, 72(%esp)
movl %edx, 76(%esp)
fildq 72(%esp)
fistpq y
lock orl $0, (%esp)
subl $1, %esi
"lock orl" очевидно исполняет роль барьера.
Можешь пояснять про это? Что ты имел ввиду под перемешиванием? Я сбился с тренда.прочитать/записать не все байты одновременно — перемешать старые и новые значения.
А тогда при чем тут кэшлайны? Если треды выполняются на одном процессоре, и запись одного байта выполняется как чтение 4, модификация, запись 4, то тред по идее в любом месте может быть прерван, в том числе после чтения 4-х байт, потом другой поток их модифицирует, хотя изменяет другой байт, и записывает, потом первый поток модифицирует свою старую копию и записывает. Где тут всплывают кэш-линейки?
трэд почитай, тут немного о другом. тут проблема с записью слов так чтоб чтение возвращало либо новое либо старое значение а не их комбинацию
набросил.
набросилвы пришли к какому-то выводу? мои заключения верны?
хорошо бы отредактировать те сообщения, которые используют баг гцц, иначе при перечитывании полная каша в голове возникает
Documentation/atomic_ops.txt
Properly aligned pointers, longs, ints, and chars (and unsigned equivalents) may be atomically
loaded from and stored to in the same sense as described for atomic_read and atomic_set.
то есть даже без volatile должно быть атомарно, а тут оказалось что даже с volatile не атомарно.
дык ждём что скажут разные гурувроде патч дали и признали свой фейл
а где "новая тема" на этом багтреккере про volatile с обсуждениями?
sched_latency_ns/sched_min_granularity_ns = 8 > 2, поэтому каждому будет даваться по 18ms/2.а не мог бы протестить раз уж с volatile так всё интересно вышло? я мерил вот так:
А если запустить 100 задач, то каждой будет по 2.25ms даваться.
while(true)
{
QueryPerformanceCounter(&T); // это обёртка к ассемблерной rdtsc
if (T - lastT > threshold) // threshold = 10000 тактов в самый раз
{
printf("%lf\n", (lastT - firstT) / 2660000.0); // делитель = частота проца в килогерцах, ответ в милисекундах
QueryPerformanceCounter(&T); // printf делается много тактов, лучше освежить T
firstT = T;
}
lastT = T;
}
для windows как и ожидал получил 0.97мс
очень желательно поставить аффинити на одно ядро
этим примером ты считаешь, через сколько ms тред получает управление, а не сколько ms он выполняется непрерывно
#include <sys/resource.h>
#include <sys/time.h>
#include <stdio.h>
int main (int argc, char **argv) {
struct rusage pusage, usage;
struct timeval ptime, time, slice;
gettimeofday(&ptime, NULL);
getrusage(RUSAGE_SELF, &pusage);
while (1) {
getrusage(RUSAGE_SELF, &usage);
if (usage.ru_nvcsw == pusage.ru_nvcsw &&
usage.ru_nivcsw == pusage.ru_nivcsw)
continue;
gettimeofday(&time, NULL);
timersub(&time, &ptime, &slice);
printf("%d.%06d %lu %lu \n", slice.tv_sec, slice.tv_usec,
usage.ru_nivcsw - pusage.ru_nivcsw,
usage.ru_nvcsw - pusage.ru_nvcsw);
gettimeofday(&ptime, NULL);
getrusage(RUSAGE_SELF, &pusage);
}
}
2 task: 0.02s
3 task: 0.027s
4 task: 0.02s
5 task: 0.01-0.02s
дальше начинаются осцилляции и максимальные слайсы растут до 0.1s возможно и дальше
то есть для 1 задачи ~10мс, и доростает до ~100мс? а что тогда такое 18мс и 2.25мс из рассчётов 'а?
/ 2660000.0); // делитель = частота проца в килогерцах, ответ в милисекундахты же троллишь, да?
В документации по QueryPerformanceCounter наверняка есть ссылка на QueryPerformanceFrequency
это частота моего процессора её быстрее напечатать чем QueryPerformanceFrequency(...)
> а не сколько ms он выполняется непрерывно
думал 6 часов .. у меня точно ошибка? можешь свой вариант кода?
это же правда важный вопрос сколько времени получает тред в непрерывное выполнение. для некоторых задач снижение кванта (или слайса) даёт прирост производительности без изменения кода.
Почему они разные получились? Разве не один должен быть промежуток, если задач не очень много?
PS: проц занят на 100% под sys, а не us. Почему так? Он gettimeofday считает системным вызовом?
после подкручивания настроек, можно снизить на 2 задачах время с 18мс до 8мс, но дальше не снижается.
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
long long threshold = 10000;
for (;;)
{
LARGE_INTEGER T, firstT;
QueryPerformanceCounter(&firstT);
while(true) //ждем перехода на новый квант
{
QueryPerformanceCounter(&T);
if (T.QuadPart - firstT.QuadPart > threshold)
{
firstT = T;
break;
}
}
LARGE_INTEGER lastT = firstT;
while(true) //ждем перехода на следующий квант
{
QueryPerformanceCounter(&T);
if (T.QuadPart - lastT.QuadPart > threshold)
{
printf("%lf ms\n", (lastT.QuadPart - firstT.QuadPart) / (double)frequency.QuadPart * 1000);
break;
}
lastT = T;
}
}
#include <sys/resource.h>
#include <sys/time.h>
#include <stdio.h>
#include <time.h>
#define THRESHOLD 1500LL
int main
{
timespec time_b1, time_e1, time_b2, time_e2, time_b, time_e;
int i=0;
long long int diff;
long long int acc_s=0;
long long int acc_s2=0;
while (1)
{
// detect first tick after jump
clock_gettime(CLOCK_MONOTONIC, &time_b1);
while(1)
{
clock_gettime(CLOCK_MONOTONIC, &time_e1);
diff = time_e1.tv_nsec - time_b1.tv_nsec + (time_e1.tv_sec-time_b1.tv_sec)*1000000000LL;
if(diff > THRESHOLD)
{
time_b = time_e1;
break;
}
time_b1 = time_e1;
}
clock_gettime(CLOCK_MONOTONIC, &time_b2);
while(1)
{
clock_gettime(CLOCK_MONOTONIC, &time_e2);
diff = time_e2.tv_nsec - time_b2.tv_nsec + (time_e2.tv_sec-time_b2.tv_sec)*1000000000LL;
if(diff > THRESHOLD)
{
time_e = time_b2;
break;
}
time_b2 = time_e2;
}
i++;
acc_s += time_b2.tv_nsec - time_e1.tv_nsec + (time_b2.tv_sec-time_e1.tv_sec)*1000000000LL;
acc_s2 += diff;
if (i%1000 == 0)
{
// printf("%d.%06d %lu %lu \n", slice.tv_sec, slice.tv_usec,
// usage.ru_nivcsw - pusage.ru_nivcsw,
// usage.ru_nvcsw - pusage.ru_nvcsw);
printf("pause:=%lld us quant: %lld us\n", acc_s2/i/1000, acc_s/i/1000);
acc_s=0;
acc_s2=0;
i = 0;
}
}
}
под линукс такой же пример показывает, что переключение раз в 1 мс, а то и реже. Где подвох?.
результат:
$ taskset 2 ./slicecheck2
pause:=8 us quant: 983 us
pause:=8 us quant: 993 us
pause:=8 us quant: 994 us
pause:=8 us quant: 1016 us
pause:=8 us quant: 952 us
pause:=308 us quant: 973 us
pause:=693 us quant: 742 us
pause:=741 us quant: 766 us
pause:=665 us quant: 753 us
pause:=912 us quant: 832 us
pause:=708 us quant: 775 us
pause:=857 us quant: 802 us
pause:=913 us quant: 900 us
чуть погодя запускаю второ( у первого в это время вырос простой):
$ taskset 2 ./slicecheck2
pause:=845 us quant: 769 us
pause:=805 us quant: 738 us
pause:=670 us quant: 711 us
pause:=845 us quant: 798 us
pause:=829 us quant: 811 us
pause:=701 us quant: 715 us
pause:=753 us quant: 749 us
pause:=488 us quant: 792 us
для четырёх процессов:
$ taskset 2 ./slicecheck2
pause:=2556 us quant: 848 us
pause:=2460 us quant: 834 us
pause:=2548 us quant: 810 us
pause:=2508 us quant: 933 us
с дефолтными параметрами
kernel.sched_child_runs_first = 0
kernel.sched_min_granularity_ns = 2250000
kernel.sched_latency_ns = 18000000
kernel.sched_wakeup_granularity_ns = 3000000
kernel.sched_tunable_scaling = 1
kernel.sched_migration_cost = 500000
kernel.sched_nr_migrate = 32
kernel.sched_time_avg = 1000
kernel.sched_shares_window = 10000000
kernel.sched_rt_period_us = 1000000
kernel.sched_rt_runtime_us = 950000
kernel.sched_autogroup_enabled = 0
а если оставить на пару минут, то получится так:
pause:=680 us quant: 694 us
pause:=728 us quant: 690 us
pause:=704 us quant: 680 us
pause:=788 us quant: 774 us
pause:=760 us quant: 770 us
pause:=724 us quant: 732 us
pause:=684 us quant: 726 us
pause:=816 us quant: 750 us
pause:=944 us quant: 897 us
pause:=3783 us quant: 3777 us
pause:=3747 us quant: 3718 us
pause:=3480 us quant: 3471 us
pause:=3488 us quant: 3514 us
pause:=3310 us quant: 3308 us
pause:=3728 us quant: 3684 us
проц горячий уже был, может из-за этого.
Оставить комментарий
Phoenix
платформа x86_64. Где написано, что делается атомарно? ( и какую атомарность можно получить в C/asm, oc: linux)int32, int64 ?
Вопрос 1:
Предположим, один процесс пишет по адресу *p (изначально там a0) данные а1, a2...
А второе по этому же адресу читает. Можно ли гарантировать, будет прочитано a0 или a1 (а не половина а0, а вторая половина от a1)?
Вопрос 2(пусть вопрос 1 решился положительно):
Один процесс пишет по адресу *pa и *pb данные по такому алгоритму.
1. изначально в *pa a0, в *pb b0; i=0
2 i+=1
3 в *pa пишется ai
4 в *pb пишется bi
5 переходим к п.2
Второй процесс читает, к примеру, B = *pb, потом A = *pa. (т.е. в обратном порядке)
Может ли так получиться (из-за какой-нибудь оптимизации процессоров и памяти что
A = a1
B = b2 ?