shared memory: Какая атомарность гарантируется

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 ?

Ivan8209

А ты какую именно разделямую память имеешь в виду?
Там есть флаги для mmap и shm, если ты хочешь семафоры делать или ещё что-нибудь.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."

Phoenix

так я как раз без семафоров и хочу.
mmap от shmat отличается? Я вторую использую, но думал, что это просто разные стандарты, приводящие к одному и тому же

evgen5555

на 2 вопрос ответ утвердительный
см. первые два параграфа из документа про барьеры:
http://www.kernel.org/doc/Documentation/memory-barriers.txt

smit1

Вопрос 1:
Предположим, один процесс пишет по адресу *p (изначально там a0) данные а1, a2...
А второе по этому же адресу читает. Можно ли гарантировать, будет прочитано a0 или a1 (а не половина а0, а вторая половина от a1)?
Если адрес выровнен, то чтение / запись 64-битного значения будут атомарными.
Вопрос 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 ?
Может так получиться, что процесс 1 уже всё записал и завершился, а второй ещё читать и не начинал.

evgen5555

Предположим, один процесс пишет по адресу *p (изначально там a0) данные а1, a2...
А второе по этому же адресу читает. Можно ли гарантировать, будет прочитано a0 или a1 (а не половина а0, а вторая половина от a1)?
атомарность, конечно же, будет на уровне машинного слова
процессор не может прочитать полслова в регистр, переключиться на другой поток и забить регистр другой половиной

ppplva

Если адрес выровнен, то чтение / запись 64-битного значения будут атомарными.
Да, если речь идет об ассемблере.
В C/C++ нет такой гарантии, даже для volatile, и нужно использовать атомики.

Marinavo_0507

А где-нибудь в одном месте собрана эта мудрость?

Lexa111

на 2 вопрос ответ утвердительный
платформа x86_64
У x86_64 реордеринг только вида "Stores reordered after loads" wiki. То есть CPU ничего "оптимизировать" не будет.
Получается, что A=a1, B=b2 может быть только благодаря компилятору, нет?

ppplva

Я не знаю одного хорошего места.
Что-то есть здесь: http://www.hpl.hp.com/techreports/2008/HPL-2008-56.pdf
Можно новый стандарт С++ читать.

ppplva

Дреппера, кстати, можно читать. Но там довольно много.

Lexa111

атомарность, конечно же, будет на уровне машинного слова
процессор не может прочитать полслова в регистр, переключиться на другой поток и забить регистр другой половиной
Да, но процессоров может быть несколько, а слово может быть порвано на две кэш-линии. Как происходит синхронизация этих линий в x86_64, я не знаю. Годные линки приветствуются :)

Dasar

Если адрес выровнен, то чтение / запись 64-битного значения будут атомарными.
разве не надо еще добавлять инструкцию, чтобы кэши процессора синхронизировались с памятью?

ava3443

Intel 64 and IA-32 Architectures
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

ava3443

разве не надо еще добавлять инструкцию, чтобы кэши процессора синхронизировались с памятью?
надо
запись надо делать locked (т.е. добавлять "lock" перед xchg, cmpxchg и прочими)
тогда любое чтение будет консистентно

evgen5555

64-битное слово помещается в кэш-линию целиком, поэтому и синхронизироваться по любому cache-coherence протоколу должно без особых проблем

ppplva

Вам устное замечание. Распространение заведомо неверной информации. утверждение (про атомарность) неверно для случая, когда используется многопроцессорная машина

Нет, это Вам устное замечание.
Запись будет _атомарной_ - никто и никогда не увидит ее выполненной частично. Вы путаете атомарность с memory ordering.

ppplva

разве не надо еще добавлять инструкцию, чтобы кэши процессора синхронизировались с памятью?
Когерентный кэш невидим для программы (кроме таймингов, конечно). Т.е. можно считать что любой значение которое ушло из процессора в его кэш напрямую попадает в память.

smit1

разве не надо еще добавлять инструкцию, чтобы кэши процессора синхронизировались с памятью?
Атомарность доступа к памяти и когерентность кэшей - это разные вещи.
То, что чтение / запись выровненного 64-битного слова атомарны означает, что невозможно такое, чтобы в общей 64-битной области памяти получилась часть байт из одной такой операции, а часть из другой. Независимо от того, на каких процессорах и в какие моменты они были выполнены, и в каких кэшах они успели побывать.

Lexa111

64-битное слово помещается в кэш-линию целиком

Если адрес этого слова выровнен — да, помещается.
Если нет — легко можно разложить в две линии: например, по адресу cacheline_size-1.

evgen5555

согласен, в такой ситуации возможен тупняк
просто в своей практике не встречался с тем, чтобы компилятор не выравнивал переменные, посему и туплю :)

Dasar

Атомарность доступа к памяти и когерентность кэшей - это разные вещи.
был не прав, действительно их попутал

Phoenix

о спасибо!
в п. 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.
или я как-то не так понял последний пример?

ppplva

Забудь про кэши, они ни на что не влияют.
Это пример о том, что x86 процессоры видят события в памяти в одном и том же порядке, с одним исключением - результат своей записи процессор всегда видит немедленно, даже если запись еще на дошла до памяти.

ppplva

Опиши подробнее что требуется, кто такие ai и bi. Может ли процесс-получатель пропускать некоторые пары?

Phoenix

пропускать можно.
читатели делают, грубо говоря,

for(;;)
{
value = *pa;
usleep(10);
}

хотелось бы, чтобы *pa было максимально возможно новое.
обновление самих pa просходят относительно редко (в 1000 раз реже чтений)

erotic

В C/C++ нет такой гарантии, даже для volatile, и нужно использовать атомики.
А что, на практике действительно возможны варианты/компиляторы, когда запись/чтение 64-битного выровненного слова будет неатомарной на x86_64?

Phoenix

когда делаешь что-то такое, так может случиться.
struct
{
int16 a;
int64_t a1;
int64_t a2;
int64_t a3;
int64_t a4;
int64_t a5;
} __attribute__packed
А это в свою очередь имеет смысл делать, когда в файл пишешь какие-нибудь бинарные структуры или читаешь

erotic

Сдается мне, что ты пропустил слово "выровненного" в моей фразе.

Maurog

А что, на практике действительно возможны варианты/компиляторы, когда запись/чтение 64-битного выровненного слова будет неатомарной на x86_64?
подозреваю, что возможны: http://stackoverflow.com/questions/5258627/atomic-64-bit-wri...
я пока склоняюсь к тому, что на наших любимых процах x86 и наших любимых компиляторах VS + gcc атомарным будет чтение\запись
1) выровненных 32 битных значений, если скомпилировано под x32
2) выровненных 32 и 64 битных значений, если скомпилировано под x64

Maurog

добавлю ссылочку на мсдн: 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.

ppplva

О, замечательная ссылка, но неверный вывод.
На 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)
...

elenangel

а если 64-битная переменная является частью packed структуры с некратным четырём смещением от начала?
upd: опередили

erotic

А вот про доступ к отдельным байтам еще интересно: он существует?
Т.е. если я пишу в один байт, это не превращается в чтение слова, изменения его сдвигами и записи опять целого слова? Т.е. если два потока пишут в разные байты одного слова, могут ли они влиять друг на друга?

elenangel

afaik, превращается, т.е. именно выбирается целое слово, часть его используется/модифицируется, потом возвращается на место. про синхронизацию при возвращении на место не знаю.

ppplva

Это произойдет на уровне строки кэша, то есть она загрузится из памяти, поменяется один байт, потом когда-нибудь запишется обратно. Протокол когерентности позаботится о том, чтобы программа этого не заметила. Т.е. программа увидит честную атомарную запись одного байта.
Компилятор иногда может сделать такую оптимизацию. Иногда не может - например, если эти байты относятся к разным location, то добавление чтения-записи соседних байтов там где этого раньше не было может внести data race и undefined behavior.
И еще есть false sharing, но это только производительность.

Phoenix

при чём тут на уровне кэша? Я так понял, что имел ввиду, что когда меняем байт, читается сразу всё слово(в регистр меняется байт и уходит обратно. И это неатомарно получится.
PS
Сейчас добавил в пример выше строки
   char *pc = reinterpret_cast<char*>(a)+1;
(*pc) = 123;

и оно расвернулось в

> movslq a(%rip %rax
> movb $123, 1(%rax)

т.е. одна инструкция на изменение байта, т.к. в регистры ничего не выгрузилось. Может я пример плохой сделал конечно.

ppplva

Да, у тебя получилась атомарная запись. Но компилятор имеет право сгенерировать загрузку и выгрузку целого слова - например, если этот байт часть более длинного инта. Дело в том, что другие потоки не могут читать это слово одновременно с записью, иначе data race и пипец.
Т.е. компилятор может сгенерировать неатомарную запись 1 байта, если он уверен что другие потоки этого не заметят. Про другие процессы он ничего знает, и через shared memory эту подставу можно увидеть. Ну и в ассемблерном листинге, конечно.

Phoenix

Т.е. то, что написал верно? Что байты менять нельзя? Просто твой ответ, что кэш всё сделает и не надо париться, или я это так понял.
Т.е. получается, что единственное, что можно читать писать это 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, но у меня такого файла нет

ppplva

Правильно использовать кошерный std::atomic, если он поддерживается в твоем компиляторе. У него есть relaxed операции, которые не обеспечивают никакой синхронизации, но гарантируют атомарность. atomic_t, насколько я понимаю, берется из ядреных хедеров на линуксе, т.е. не факт что он везде есть (точно нет в винде).
Если нет, то http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Atomic-Builtins....

bleyman

Вы оба по-моему не можете внятно сформулировать вопрос.
Может ли так получиться что в результате записи одного байта попортятся соседние байты?
Поток 1 делает mem[0x100001] = 23
Поток 2 делает mem[0x100000] = 17, которое реализуется как зачитывание 4 байтов с 0x100000 по 0x100003, изменение первого, записывание всех четырёх обратно. В результате изменение сделанное первым потоком может потеряться, хотя логически они доступались к разным адресам и не должны были конфликтовать.
Так может получиться? Даже в двух частях: может ли подставу устроить железо и может ли подставу устроить компилятор?

Phoenix

вроде же ответили, что железо не может, а компилятор может?
Т.е. компилятор может сгенерировать неатомарную запись 1 байта

Это пример о том, что x86 процессоры видят события в памяти в одном и том же порядке, с одним исключением - результат своей записи процессор всегда видит немедленно, даже если запись еще на дошла до памяти.

state7401281

другой вопрос, но тоже про линукс:
а сколько максимум времени может работать один тред на одном одноядерном 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: я уже два года хочу об этом узнать.

smit1

PS: я уже два года хочу об этом узнать.
За два года не смог открыть гугль и написать что-нибудь типа "linux scheduler quantum"? :D
И сдаётся мне, инфа про 1 мс в виндоус неверна примерно на порядок.

Dasar

> T2k+1 - T2k?
это время называется timeslice или quantum
в виндовс это ~1мс
на windows-е гранулярность scheduler-а - 1мс, но время timeslice-а для тредов с нормальным приоритетом - 10мс для клиентской системы, и 100мс для серверной

Phoenix

тут пишут, что http://doc.opensuse.org/documentation/html/openSUSE/opensuse...
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 даваться.

vall

кроме таймслайсов ещё важно где может произойти переключение контекста: в обычных ядрах текущая задача вытесняется только если она в юзерспейсе, а в preemptive — в любом неатомарном ядерном контексте. когда допишут full dyntick периодический таймер исчезнет вообще.

vall

см. первые два параграфа из документа про барьеры:
http://www.kernel.org/doc/Documentation/memory-barriers.txt
ага правильный источник знаний. ссылаться на на утверждения из C или тем более С++ странно, их писали не для этого. то что существуют архитектуры где сложение работает только с насыщением а чтение двойного слова не атомарно относительно прерывания конечно прекрасно, но кого это волнует..
данные ходят кэшлайнами так что одна инструкция чтения/записи не может перемешать байты в слове,
и если где-то это не так то я бы хотел посмотреть на автора и как он там будет реализовывать хоть какую-то многозадачность или не дай боже 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)

Phoenix

full dyntick

Можно в двух словах что это? и как оно исчезнуть может вообще? Я что-то ничего понятного не могу нагуглить.

vall

Это полный nohz — сейчас периодический таймер не тикает только когда процессор уходит в idle. Взводится one-shot таймер на ближайшее событие. С full dyntick периодического таймера не будет вообще и когда процессор уходит в юзерспейс тоже будет взводиться one-shot на конец таймслайса.
http://lkml.org/lkml/2012/12/17/177

Phoenix

интересно. Я думал, что сейчас LOC как раз и есть же этот one-shot таймер. А он получаетя всё-таки тикает, когда проц работает.

 
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



vall

ну фактически он one-shot но тикает не реже HZ раз в секунду если процессор не в idle-state т.к. постоянно есть периодическое событие.

ppplva

данные ходят кэшлайнами так что одна инструкция чтения/записи не может перемешать байты в слове,
и если где-то это не так то я бы хотел посмотреть на автора и как он там будет реализовывать хоть какую-то многозадачность или не дай боже smp.
Я правильно понимаю, ты хочешь сказать что в
 volatile unsigned long long x;
x = 0x1111111111111111;

из другого треда/процесса невозможно увидеть половину байтов нулей, а половину единиц? И дело в том что x целиком лежит в одной строке кэша?

vall

выше сказано только про лонги. лонг лонг на 32-битных машинах не всегда можно записать и прочитать одной инструкцией. на и386 есть какой-то хак (лень смотреть где и какой)

ppplva

Там сказано pointers. Считаем что машина - x86_64.
Атомарно или нет?

vall

да, атомарно. BTW sizeof(void*) == sizeof(long)
но слово должно быть выровнено.

ppplva

Держи.
#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

vall

Чувак, на этом утверждении держится всё RCU в ядре. Мне конечно интересно где ты облажался, но не сейчас.

procenkotanya

Похоже, этот код демонстрирует баги в компиляторе. Мало того, что записи в volatile бьются на две, так ещё и (на 4.6.3 последующие записи в a, b, ... h1 удаляются, типа, как избыточные. , есть желание зафайлить баги на gcc.gnu.org? Если нет, я на днях займусь.

ppplva

Чтобы меня там назвали идиотом, не понимающим стандарт? Нет, спасибо )
Записи он удаляет совершенно справедливо - это unreachable code.
Щас исправлю пример, чтобы он переживал -O3.

procenkotanya

Удаляет справедливо, да, что-то я на условие цикла внимание не обратил.
Идиотом не назовут, разделение volatile write'ов на два это серьёзная проблема quality of implementation, даже если формально допускается буквой стандарта.

ppplva

Хм, такой аргумент я могу понять, к тому же Clang этого не делает - наверное просто не умеет ;)
Cможешь объяснить как в таком случает работает RCU в ядре? rcu_assign_pointer не содержит ни одного volatile. Или я не там ищу?

vall

Мало того, что записи в volatile бьются на две
ну и прекрасно. он что какие-то короткие мувы решил использовать раз половинки мелкие?
если он так-же NULL половинками будет присваивать то наступит суровая жопа.

ppplva

Т.е. уже понятно, что он там на честном слове держится.
Речь о том, что, кажется, исправление volatile в gcc ему не поможет.

ppplva

Он решил использовать короткие мувы, потому что immediate длинный, а register pressure высокий. Для NULL это не актуально.

vall

Т.е. уже понятно, что он там на честном слове держится.
Речь о том, что, кажется, исправление volatile в gcc ему не поможет.
ага, нужно чинить и гсс и ядро.
уже третий баг найденный в этом трэде — первый что документация на кернел.орг не обновлялась с 3.0

vall

Он решил использовать короткие мувы, потому что immediate длинный, а register pressure высокий. Для NULL это не актуально.
а то что тут цикл это для него не повод отжать регистры?

procenkotanya

На 4.6 с включенной оптимизацией так и происходит. Выше видно, что с -O0 компилял.
Но вообще цикл это не очень серьёзный повод в данном случае, потому что альтернатива — использовать 0 регистров.

erotic

данные ходят кэшлайнами так что одна инструкция чтения/записи не может перемешать байты в слове
Можешь пояснять про это? Что ты имел ввиду под перемешиванием? Я сбился с тренда.

Lexa111


< while(true)
---
> for (int i = 0; i < 10000000; i++)

И g++ -O[23] фейлит (g++ v4.7.2). Круто, чо =)
Upd: icc v13.0.1 не фейлит.

ppplva

Да, я именно так сделал (обновил пост и теперь gcc 4.7.2 с любыми флагами разбивает стор на 2.

state7401281

> на windows-е гранулярность scheduler-а - 1мс, но время timeslice-а для тредов
> с нормальным приоритетом - 10мс для клиентской системы, и 100мс для серверной
если запустить 2 треда, которые конкурируют _только_ за проц, я на 99.9% (из практики) уверен, что они будут выполнться по 1мс, а не по 10мс или 100мс. собственно вопрос - а что тогда такое слайс и когда он хоть как-то проявляется?

erotic

Щас исправлю пример, чтобы он переживал -O3
Я так понимаю, что пример ты уже поправил? Потому что я полчаса уже ломаю голову, с чего там unreachable-code и почему у меня в asm'е он не выкидывается :(

state7401281

> при этом 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 даваться.
а кто-нибудь пробовал поменять и замерить? а то я про винду тоже нагугливал и про 10мс и про 100мс, а на деле оказалась 1мс ...
с линуксами к сожалению из-за того, что их 100500 видов я не смог выбрать какой установить, чтобы проверить :(

ppplva

ага, там был бесконечный цикл для надежности :)

Phoenix

Возвращаясь к первому вопросу:
 $ 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

как поправить?

ppplva

Опаньки.
Это уже явно бага. Он делает все то же самое что для volatile, но добавляет mfence :grin:
Кстати, вот как этот цикл выглядит для 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

ppplva

"lock orl" очевидно исполняет роль барьера.

vall

Можешь пояснять про это? Что ты имел ввиду под перемешиванием? Я сбился с тренда.
прочитать/записать не все байты одновременно — перемешать старые и новые значения.

erotic

А тогда при чем тут кэшлайны? Если треды выполняются на одном процессоре, и запись одного байта выполняется как чтение 4, модификация, запись 4, то тред по идее в любом месте может быть прерван, в том числе после чтения 4-х байт, потом другой поток их модифицирует, хотя изменяет другой байт, и записывает, потом первый поток модифицирует свою старую копию и записывает. Где тут всплывают кэш-линейки?

vall

трэд почитай, тут немного о другом. тут проблема с записью слов так чтоб чтение возвращало либо новое либо старое значение а не их комбинацию

Maurog

набросил
вы пришли к какому-то выводу? мои заключения верны?
хорошо бы отредактировать те сообщения, которые используют баг гцц, иначе при перечитывании полная каша в голове возникает

vall

дык ждём что скажут разные гуру. полтора года назад множество уважаемых людей пришло к выводу:
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 не атомарно.

Maurog

дык ждём что скажут разные гуру
вроде патч дали и признали свой фейл :shocked:

Phoenix

а где "новая тема" на этом багтреккере про volatile с обсуждениями?

state7401281

sched_latency_ns/sched_min_granularity_ns = 8 > 2, поэтому каждому будет даваться по 18ms/2.
А если запустить 100 задач, то каждой будет по 2.25ms даваться.
а не мог бы протестить раз уж с volatile так всё интересно вышло? я мерил вот так:

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мс
очень желательно поставить аффинити на одно ядро

Dasar

этим примером ты считаешь, через сколько ms тред получает управление, а не сколько ms он выполняется непрерывно

vall

#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);
}
}

vall

1 task: 0.01s
2 task: 0.02s
3 task: 0.027s
4 task: 0.02s
5 task: 0.01-0.02s
дальше начинаются осцилляции и максимальные слайсы растут до 0.1s возможно и дальше

state7401281

то есть для 1 задачи ~10мс, и доростает до ~100мс? а что тогда такое 18мс и 2.25мс из рассчётов 'а?

smit1

/ 2660000.0); // делитель = частота проца в килогерцах, ответ в милисекундах
ты же троллишь, да?
В документации по QueryPerformanceCounter наверняка есть ссылка на QueryPerformanceFrequency

state7401281

это частота моего процессора её быстрее напечатать чем QueryPerformanceFrequency(...)

state7401281

> этим примером ты считаешь, через сколько ms тред получает управление,
> а не сколько ms он выполняется непрерывно
думал 6 часов .. у меня точно ошибка? можешь свой вариант кода?
это же правда важный вопрос сколько времени получает тред в непрерывное выполнение. для некоторых задач снижение кванта (или слайса) даёт прирост производительности без изменения кода.

Phoenix

я правильно понимаю, что в случае 2 и 3 каждый процесс имеет по 10мс и 9мс соответвенно, а общее время, которое планироващик распределяет между задачами это как раз 20мс и 27 мс?
Почему они разные получились? Разве не один должен быть промежуток, если задач не очень много?
PS: проц занят на 100% под sys, а не us. Почему так? Он gettimeofday считает системным вызовом?
после подкручивания настроек, можно снизить на 2 задачах время с 18мс до 8мс, но дальше не снижается.

Dasar


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;
}
}

Phoenix

в общем, не понравился мне этот системный вызов и я переписал всё через userspace:
 

#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

проц горячий уже был, может из-за этого.
Оставить комментарий
Имя или ник:
Комментарий: