Из интересного:
* Аллокация объектов: в лучшем случае new() вместе с выделением памяти укладывается в 9 asm-инструкций
* Минимальный размер объекта — 12 байт (для x86). Как бы известный факт, но в куче блогов почему-то пишут только про 8 байт заголовка.
* Создание объекта с финалайзером заметно дороже. Тоже известно, но напомнить не помешает.
Кое-что было у других авторов и у самого Ben Watson в его блоге, но в виде одной короткой статьи не попадалось. По уровню напоминает классику типа %subj% UNDOCUMENTED от Wesner Moise или .NET Type Internals от Adityanand.
Здравствуйте, Sinix, Вы писали:
S>* Аллокация объектов: в лучшем случае new() вместе с выделением памяти укладывается в 9 asm-инструкций
Непонятно с чем связанно удивление — это много или мало?
У Compacting GC выделение памяти на Happy-Path это, фактически, просто увеличение указателя на требуемый размер
Здравствуйте, Evgeny.Panasyuk, Вы писали:
S>>* Аллокация объектов: в лучшем случае new() вместе с выделением памяти укладывается в 9 asm-инструкций EP>Непонятно с чем связанно удивление — это много или мало?
Скорее, "если сильно постараться, то можно впихнуть, но всё равно круто"
EP>У Compacting GC выделение памяти на Happy-Path это, фактически, просто увеличение указателя на требуемый размер
Угу. Только
1. Надо ещё загрести размер типа (несложно, сборка гарантированно загружена в момент jit-компиляции).
2. Выбрать текущий буфер для аллокации (тут есть нюансы).
3. Заполнить заголовок объекта (снова несложно).
Cам код принципиально прост. Все "динамические" значения достаются по захардкоженным адресам. Нет никаких проверок на потокобезопасность, т.к. каждый поток работает со своей областью в gen0.
Но объём архитектурных решений, необходимых чтобы это "да там всё просто" работало, внушает уважение
Здравствуйте, Sinix, Вы писали:
S>1. Надо ещё загрести размер типа (несложно, сборка гарантированно загружена в момент jit-компиляции).
А разве размер типа не известен во время компиляции? Или хотя бы во время JIT? Там по хорошему не должно быть хождения в память за размером типа.
Или размер типа может поменяться во время работы программы?
S>2. Выбрать текущий буфер для аллокации (тут есть нюансы).
Всего лишь обращение к TLS — mov edx, dword ptr fs:[0E30h], то есть tls.current_allocator.
S>Cам код принципиально прост. Все "динамические" значения достаются по захардкоженным адресам. Нет никаких проверок на потокобезопасность, т.к. каждый поток работает со своей областью в gen0. S>Но объём архитектурных решений, необходимых чтобы это "да там всё просто" работало, внушает уважение
Нужен всего лишь precise garbage collection, и только — всё остальное вытекает из этого. Нет там "объёма архитектурных решений".
Здравствуйте, Evgeny.Panasyuk, Вы писали:
S>>1. Надо ещё загрести размер типа (несложно, сборка гарантированно загружена в момент jit-компиляции). EP>А разве размер типа не известен во время компиляции?
Нет, в следующей версии сборки в класс может добавиться/удалиться поле.
EP>Или хотя бы во время JIT? Там по хорошему не должно быть хождения в память за размером типа.
Угу, в этот момент и хардкодится. Писал выше: "несложно, сборка гарантированно загружена в момент jit-компиляции".
EP>Или размер типа может поменяться во время работы программы?
Если забыть про строки, массивы и объекты DLR — не может. По крайней мере, в текущей версии CLR точно. И в будущих — очень сомневаюсь, что появится.
S>>2. Выбрать текущий буфер для аллокации (тут есть нюансы). EP>Всего лишь обращение к TLS — mov edx, dword ptr fs:[0E30h], то есть tls.current_allocator.
Угу.
EP>Нужен всего лишь precise garbage collection, и только — всё остальное вытекает из этого. Нет там "объёма архитектурных решений".
Это до момента, когда идею из академической статьи надо превратить в стандарт, который проживёт без особых изменений 15 лет. Вот это и внушает.
Здравствуйте, Sinix, Вы писали:
EP>>Или размер типа может поменяться во время работы программы? S>Если забыть про строки, массивы и объекты DLR — не может. По крайней мере, в текущей версии CLR точно. И в будущих — очень сомневаюсь, что появится.
То есть размер типа можно передавать напрямую в new, без лишних обращений к памяти. Если же всё заинлайнить, то получается (C++ с подобным аллокатором):
// get top of free space from TLS allocator
mov rbx, QWORD PTR fs:free_space_top@tpoff
// increase by sizeof(Abstract)=8
lea rax, [rbx+8]
// check for bounds
cmp rax, QWORD PTR fs:total@tpoff
jae .SLOW_PATH
.L9:
// update top
mov QWORD PTR fs:free_space_top@tpoff, rax
// put virtual table pointer
mov QWORD PTR [rbx], OFFSET FLAT:_ZTV8Abstract+16
// result pointer is in rbx
6 инструкций vs 9+3, но это мелочь по сравнению с тем, что тут ещё и на одно обращение к памяти меньше.
Здравствуйте, Sinix, Вы писали:
S>На codeproject появилась небольшая статья от Ben Watson (это который Writing High-Performance .NET Code).
S>Из интересного: S>* Аллокация объектов: в лучшем случае new() вместе с выделением памяти укладывается в 9 asm-инструкций
Выделение принципиально неправильно рассматривать отдельно от издержек на использование и в частности на освобождение. Скажем, после аллокации всегда есть использование. В зависимости от того, кто будет ссылаться на новый объект, издержки на ту же аллокацию резко меняются. Если добавить минимальную инициализацию, то получается что даже эти 9 инструкций роли не играют. Получить профит можно, но массовой пользы такие знания не несут.
S>* Минимальный размер объекта — 12 байт (для x86). Как бы известный факт, но в куче блогов почему-то пишут только про 8 байт заголовка.
Пишут в основном про оверхед, а не про минимальный размер объекта. Просто никому не приходило в голову замерять размер пустого объекта. Случай вобщем вырожденый.
Здравствуйте, Ikemefula, Вы писали:
S>>* Минимальный размер объекта — 12 байт (для x86). Как бы известный факт, но в куче блогов почему-то пишут только про 8 байт заголовка.
I>Пишут в основном про оверхед, а не про минимальный размер объекта. Просто никому не приходило в голову замерять размер пустого объекта. Случай вобщем вырожденый.
boxing же, блин
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, BulatZiganshin, Вы писали:
S>>Но объём архитектурных решений, необходимых чтобы это "да там всё просто" работало, внушает уважение BZ>да это старндартное решение. в хаскеле сделано точно так же
Когда я копался в GHC мне так не показалось, но копался очень поверхностно, могу и ошибаться. Емнип, там свои особенности и для CLR они ооочень слабо подходят. Сам allocate явно на 9 инструкций не тянет. Аллокация блоков тоже своеобразная, гранулярность внутри mblock по умолчанию — блоками по 4кб (см allocBlock). Если не вру (вот тут совсем не уверен) для старших поколений и LOB аллокации ещё и обёрнуты в локи ACQUIRE_SM_LOCK. Сравниваем с gc segments по 64кб в CLR
Disclaimer: я ни в коем случае не собираюсь оценивать GHC с точки зрения потребностей хаскеля, речь именно про "стандартное решение, всё как в CLR".
Из того, что ещё сходу вспомнил: т.к. структуры в основном immutable, обратных ссылок между поколениями практически нет, для редких исключений можно вообще не напрягаться. Для дотнета решение с линейно падающей производительностью не пройдёт, приходится извращаться. Вот описание (см "Making Generations Work with Write Barriers"), вот подробности текущей реализации.
Здравствуйте, Философ, Вы писали:
Ф>boxing же, блин
Тут ни при чём.
Речь про
class A {}
class B { int x; }
class C { int x; int y; }
// ...var a = new A(); // takes 12 (24) bytes.var b = new B(); // takes 12 (24) bytes.var c = new C(); // takes 16 (24) bytes.
Здравствуйте, Философ, Вы писали:
S>>>* Минимальный размер объекта — 12 байт (для x86). Как бы известный факт, но в куче блогов почему-то пишут только про 8 байт заголовка.
I>>Пишут в основном про оверхед, а не про минимальный размер объекта. Просто никому не приходило в голову замерять размер пустого объекта. Случай вобщем вырожденый.
Ф>boxing же, блин
Здравствуйте, Sinix, Вы писали:
S>Сам allocate явно на 9 инструкций не тянет.
почему не тянет? я там насчитал 9 мопов (12 инструкций) в основном пути. разумеется при условии что этот код будут инлайнить как в CLR. из них 2 мопа (3 инструкции) можно безболезненно выкинуть, храня
bd->end = bd->start + min(BLOCK_SIZE_W, LARGE_OBJECT_THRESHOLD)
Здравствуйте, BulatZiganshin, Вы писали:
BZ>почему не тянет? я там насчитал 9 мопов (12 инструкций) в основном пути. разумеется при условии что этот код будут инлайнить как в CLR.
Угу, перечитал код внимательней — всё ок там, с "на 9 не тянет" я явно поторопился