Ну, да, что с точки зрения использования это обычный массив с типом
extern const struct shell_cmd __shell_cmds[];
Но это, конечно, более актуально в случае NULL-терминированных массивов указателей, где нам необязательно знать размер массива, и можно обойтись без ARRAY_SPREAD_SIZE.
Вообще создаётся впечатление, что мы идём параллельными, но разными путями.
Не без этого =)
Мы когда-то тоже использовали решение точь-в-точь как у вас. Проблема в том, что с добавлением каждой новой подсистемы (тесты, драйверы, файловые системы, обработчики протоколов сетевого стека, ...) каждый раз приходилось править линкер-скрипт. Поискал сейчас по проекту, нашел 30 разных массивов, определнных через ARRAY_SPREAD_DEF. Кроме того, мы используем эту технику под капотом своего фреймворка юнит-тестирования, чтобы определять новые тест-кейсы без явной регистрации: вот тутalexkalmuk разбирал это.
С другой стороны, есть мысли (у меня, по крайней мере) отказываться от этой штуки, там, где можно без этого обойтись, например, в пользу явной генерации исходного кода билд-системой. Ну, то есть не злоупотреблять всей этой макро-магией: если посмотреть на историю файла с реализацией, мы хорошенько огребли, например, проблем с разными версиями компиляторов.
antonkozlov ниже уже упомянул Embox, хочу только подкинуть идею, как можно формировать константный массив из разных исходников. Под капотом у нас тоже используются секции компоновщика, но в исходниках массив представляется именно Си-шным массивом, а кроме того, один раз доработав линкер-скрипт, можно создавать и использовать сколько угодно разных массивов.
Определяем сам массив (один раз, например, в шелле):
Когда нужно проитерироваться по массиву, используем константу ARRAY_SPREAD_SIZE(__shell_cmds). Помимо этого, есть возможность определения NULL-терминированных (или не NULL-, а чем-нибудь другим терминированных) массивов, что тоже бывает удобно для итерирования.
Исходники в двух хедерах тут и тут, а линкер-скрипт вот:
Если нужен клон процесса с отдельным адресным пространством, то это fork. А зачем vfork?
vfork — это если нужен процесс с отдельным адресным пространством, но не полноценный клон, а достаточный лишь для выполнения вызова exec(), который очень часто следует за fork'ом. И в таком смысле vfork можно назвать оптимизацией.
В общем же смысле это конечно не оптимизация, какой-нибудь андроидовый zygote можно реализовать только fork'ом.
Я бы назвал vfork костылём, но пришедшимся, видимо, очень кстати, поскольку он покрывает очень частый сценарий использования fork в связке с exec, достигая того же самого, но меньшей кровью.
Напишу, что меня сбило с толку в своих собственных рассуждениях.
Проблема в том, что второму потоку пофиг на этот synchronized, он до него не доходит, потому что идет в обход через DummyInstance, минуя все барьеры, необходимые для чтения консистентного содержимого целевого экземпляра. Барьеры должны идти парами, если в одном потоке есть барьер на запись, а в другом нет барьера на чтение, то смысла в первом барьере нет.
Я исходил из того, что без барьера на чтение во втором потоке код будет заведомо неправильным. Но я не учёл, что если две операции чтения зависимы между собой, то на большинстве архитектур (кроме DEC Alpha) для сериализации таких операций чтения между собой явный барьер не требуется.
В данном случае зависимые операции — это чтение ссылки на accessor и чтение (разыменование) final поля этого объекта. Иными словами, барьеры здесь так или иначе всё-равно идут парой: в первом потоке барьером на запись является freeze action, во втором — неявный барьер чтения через data dependency.
Plus the special final-field rule requiring a StoreStore barrier in x.finalField = v; StoreStore; sharedRef = x;
Data Dependency and Barriers
The need for LoadLoad and LoadStore barriers on some processors interacts with their ordering guarantees for dependent instructions. On some (most) processors, a load or store that is dependent on the value of a previous load are ordered by the processor without need for an explicit barrier. This commonly arises in two kinds of cases, indirection: Load x; Load x.field
and control Load x; if (predicate(x)) Load or Store y;
Processors that do NOT respect indirection ordering in particular require barriers for final field access for references initially obtained through shared references: x = sharedRef; ... ; LoadLoad; i = x.finalField;
Conversely, as discussed below, processors that DO respect data dependencies provide several opportunities to optimize away LoadLoad and LoadStore barrier instructions that would otherwise need to be issued. (However, dependency does NOT automatically remove the need for StoreLoad barriers on any processor.)
Initialization safety guarantees that for properly constructed objects, all threads will see the correct values of final fields that were set by the constructor, regardless of how the object is published. Further, any variables that can be reached through a final field of a properly constructed object (such as the elements of a final array or the contents of a HashMap referenced by a final field) are also guaranteed to be visible to other threads. For objects with final fields, initialization safety prohibits reordering any part of construction with the initial load of a reference to that object. All writes to final fields made by the constructor, as well as to any variables reachable through those fields, become “frozen” when the constructor completes, and any thread that obtains a reference to that object is guaranteed to see a value that is at least as up to date as the frozen value. Writes that initialize variables reachable through final fields are not reordered with operations following the post-construction freeze.
Похоже, я неправ. Final даёт больше гарантий, чем я думал. Извините, что ввёл в заблуждение.
С DummyInstance проблем никаких, получить null через get() не получится. Но через get() можно получить объект, конструктор которого еще не выполнен (точнее сказать, результат его выполнения не закоммичен или не виден этому потоку).
То есть если добавить к классу Singleton не-final полей, то их инициализация не гарантируется.
public final class Singleton {
private int answer = 42;
...
}
// thread 1:
s1 = Singleton.getInstance(); // creates a new intance
assert s1.answer == 42; // works fine: sequential consistency
// thread 2:
s2 = Singleton.getInstance(); // returns an instance created by thread 1
assert s2.answer == 42; // may fail: s2.answer can be 0 (the default for int)
То есть DummyInstance инициализирован, но объект, на который ссылается его final instance, ещё нет.
Для остальных (если они не используют барьеры) никаких гарантий нет.
Ну, как так нет? А нафиг он тогда иначе нужен? :) Внутри одного потока у нас и так гарантируется sequential consistency.
И насколько я понимаю, реализуется freeze action через те же самые барьеры.
Единственный способ прочитать там что-то не то — это позвать get на неинициализированном экземпляре DummyInstance.
Если не использовать небезопасную публикацию this изнутри конструктора DummyInstance, то у вас никак не получится добраться до неинициализированного (в части final полей) объекта из другого потока. В этом суть freeze action.
конструктор еще тупо не успел выполниться
Успеть-то он успел. И эффект от его выполнения в части инициализации final полей (чего не скажешь о не-final полях, если бы они были) будет виден любому потоку, который сумел получить ссылку на DummyInstance.
Ведь сама же JVM не меняет местами вызов конструктора и запись в поле. Это скорее удобная метафора для описания последствий того, что может произойти в железе из-за особенностей протокола когерентности кэшей, если не использовать барьеры.
Поле instance для данного конкретного экземпляра записывается только один раз (причем под synchronized), так что я не вижу тут проблем.
Проблема в том, что второму потоку пофиг на этот synchronized, он до него не доходит, потому что идет в обход через DummyInstance, минуя все барьеры, необходимые для чтения консистентного содержимого целевого экземпляра. Барьеры должны идти парами, если в одном потоке есть барьер на запись, а в другом нет барьера на чтение, то смысла в первом барьере нет.
По-моему, тут вы ошибаетесь, всё же тут есть freeze action после выполнения конструктора, который сериализует инициализацию поля instance и публикацию accessor'а. Проблема в публикации самого экземпляра через поле instance.
Но при этом, насколько я понимаю, не гарантируется целостность самого этого экземпляра. Во время создания первым потоком экземпляра второй поток может прочитать уже обновленный accessor (он скорее всего опубликован безопасно, да), а через него добраться до самого экземпляра, который будет ещё находиться в процессе конструирования (потому что о безопасности его публикации мы, в общем-то, ничего сказать уже не можем).
Volatile accessor, скорее всего, спасёт ситуацию, определив happens-before между первым и вторым потоком. Если мыслить в терминах барьеров, а не спецификации, запись в volatile имеет release семантику, что означает, что никакие операции записи (например, инициализация полей нашего экземпляра при конструировании) не «утекут» вниз через барьер, который ставит запись volatile accessor'а. У чтения volatile acquire семантика, так что со вторым потоком тоже должно быть всё в порядке.
P.S. Не джавист, мог налажать (и скорее всего ...). JMM — штука непростая для понимания, и я бы, наигравшись в «ещё одну» реализацию синглтона из академического интереса, выкинул это всё к чертям, и использовал старый добрый synchronized, ну, может быть с volatile double check locking. :)
Спасибо за ответ. Про кратность расстояния в кластере из статьи я не понял, теперь вроде сообразил.
Про поворот я имел в виду, что все изображения (включая исходное) поворачиваются, чтобы вписаться в заранее заданную форму. Если вернуться к предложенной картинке, то грубо говоря, нужно уложить Лену горизонтально, головой, например, вправо, независимо от ее исходного положения, тем самым вписав в фиксированную геометрическую фигуру ее признаки (вы сами предложили этот пример) :).
Если фигура, в которую вписываются точки локальных признаков, — квадрат, то да, нужно будет делать 4 сравнения (а то и 8 для поддержки зеркального отражения картинки). Скорее всего количество сравнений можно уменьшить, договорившись, что центр масс всех вписанных точек должен оказаться, скажем, в первом квадранте квадрата.
Немного сумбурно описал, но надеюсь, сумел передать идею.
А если как-нибудь нормализовать геометрию по локальным признакам (наверное даже после кластеризации), например, вписывать фигуру, задаваемую центроидами кластеров (или их крайними точками), в какую-нибудь заранее заданную фигуру (квадрат или треугольник), и хэшировать уже такие нормализованные изображения? Тогда по идее хэш будет инвариантен к масштабированию и поворотам. Правда, все-равно непонятно, как быть с кропами, когда отрезается часть точек кластера, и центр «съезжает».
Вот такой вопрос, а использовал ли кто-либо из читателей такую схему компиляции или хотя бы слышал о ней?
Используем промежуточную линковку (ld -r) объектников, сгруппированных по подсистемам. Правда, не в целях оптимизации производительности, а для шаманства с секциями (метки начала/конца, чексуммы и т.д.). Хотя оптимизация всего этого тоже интересна. :)
А вы не в курсе, GCC умеет такие штуки? Можно ли оптимизировать несколько объектников вместе, получив объединенный и релоцируемый (то есть как -Qipo-c, наверное)? Насколько я понимаю, -flto (аналог -Qipo?) годится только для whole program и создает исполняемый файл.
PAE — это адресация физической памяти. Насколько мне известно, виртуально (в пределах одного процесса) адресовать по-прежнему можно только 4GB. Зато можно запустить больше таких процессов, которые суммарно будут использовать больше 4GB без необходимости свопиться.
В вашем случае вероятнее всего прокси переопределяет геттер для __class__, тип при этом действительно не меняется.
Попробуйте в консоли:
>>> class A(object): pass
...
>>> class B(object): pass
...
>>> a = A()
>>> a.__class__ is type(a) is A
True
>>> a.__class__ = B
>>> a.__class__ is type(a) is B
True
Но это, конечно, более актуально в случае NULL-терминированных массивов указателей, где нам необязательно знать размер массива, и можно обойтись без ARRAY_SPREAD_SIZE.
Не без этого =)
Мы когда-то тоже использовали решение точь-в-точь как у вас. Проблема в том, что с добавлением каждой новой подсистемы (тесты, драйверы, файловые системы, обработчики протоколов сетевого стека, ...) каждый раз приходилось править линкер-скрипт. Поискал сейчас по проекту, нашел 30 разных массивов, определнных через ARRAY_SPREAD_DEF. Кроме того, мы используем эту технику под капотом своего фреймворка юнит-тестирования, чтобы определять новые тест-кейсы без явной регистрации: вот тут alexkalmuk разбирал это.
С другой стороны, есть мысли (у меня, по крайней мере) отказываться от этой штуки, там, где можно без этого обойтись, например, в пользу явной генерации исходного кода билд-системой. Ну, то есть не злоупотреблять всей этой макро-магией: если посмотреть на историю файла с реализацией, мы хорошенько огребли, например, проблем с разными версиями компиляторов.
Определяем сам массив (один раз, например, в шелле):
В каждом исходнике определяем структуру и заносим её в этот массив:
Когда нужно проитерироваться по массиву, используем константу ARRAY_SPREAD_SIZE(__shell_cmds). Помимо этого, есть возможность определения NULL-терминированных (или не NULL-, а чем-нибудь другим терминированных) массивов, что тоже бывает удобно для итерирования.
Исходники в двух хедерах тут и тут, а линкер-скрипт вот:
От билд-системы дополнительно ничего не требуется (ну, кроме добавления этого линкер-скрипта).
В своё время помогла разобраться.
vfork — это если нужен процесс с отдельным адресным пространством, но не полноценный клон, а достаточный лишь для выполнения вызова exec(), который очень часто следует за fork'ом. И в таком смысле vfork можно назвать оптимизацией.
В общем же смысле это конечно не оптимизация, какой-нибудь андроидовый zygote можно реализовать только fork'ом.
Я бы назвал vfork костылём, но пришедшимся, видимо, очень кстати, поскольку он покрывает очень частый сценарий использования fork в связке с exec, достигая того же самого, но меньшей кровью.
Я исходил из того, что без барьера на чтение во втором потоке код будет заведомо неправильным. Но я не учёл, что если две операции чтения зависимы между собой, то на большинстве архитектур (кроме DEC Alpha) для сериализации таких операций чтения между собой явный барьер не требуется.
В данном случае зависимые операции — это чтение ссылки на accessor и чтение (разыменование) final поля этого объекта. Иными словами, барьеры здесь так или иначе всё-равно идут парой: в первом потоке барьером на запись является freeze action, во втором — неявный барьер чтения через data dependency.
В The JSR-133 Cookbook for Compiler Writers есть неплохое объяснение того, как это всё устроено под капотом (спасибо Fomka за ссылку):
Отсюда:
Похоже, я неправ. Final даёт больше гарантий, чем я думал. Извините, что ввёл в заблуждение.
То есть если добавить к классу Singleton не-final полей, то их инициализация не гарантируется.
То есть DummyInstance инициализирован, но объект, на который ссылается его final instance, ещё нет.
Надеюсь, сумел объяснить.
Ну, как так нет? А нафиг он тогда иначе нужен? :) Внутри одного потока у нас и так гарантируется sequential consistency.
И насколько я понимаю, реализуется freeze action через те же самые барьеры.
Если не использовать небезопасную публикацию this изнутри конструктора DummyInstance, то у вас никак не получится добраться до неинициализированного (в части final полей) объекта из другого потока. В этом суть freeze action.
Успеть-то он успел. И эффект от его выполнения в части инициализации final полей (чего не скажешь о не-final полях, если бы они были) будет виден любому потоку, который сумел получить ссылку на DummyInstance.
Ведь сама же JVM не меняет местами вызов конструктора и запись в поле. Это скорее удобная метафора для описания последствий того, что может произойти в железе из-за особенностей протокола когерентности кэшей, если не использовать барьеры.
Проблема в том, что второму потоку пофиг на этот synchronized, он до него не доходит, потому что идет в обход через DummyInstance, минуя все барьеры, необходимые для чтения консистентного содержимого целевого экземпляра. Барьеры должны идти парами, если в одном потоке есть барьер на запись, а в другом нет барьера на чтение, то смысла в первом барьере нет.
Volatile accessor, скорее всего, спасёт ситуацию, определив happens-before между первым и вторым потоком. Если мыслить в терминах барьеров, а не спецификации, запись в volatile имеет release семантику, что означает, что никакие операции записи (например, инициализация полей нашего экземпляра при конструировании) не «утекут» вниз через барьер, который ставит запись volatile accessor'а. У чтения volatile acquire семантика, так что со вторым потоком тоже должно быть всё в порядке.
P.S. Не джавист, мог налажать (и скорее всего ...). JMM — штука непростая для понимания, и я бы, наигравшись в «ещё одну» реализацию синглтона из академического интереса, выкинул это всё к чертям, и использовал старый добрый synchronized, ну, может быть с volatile double check locking. :)
Про поворот я имел в виду, что все изображения (включая исходное) поворачиваются, чтобы вписаться в заранее заданную форму. Если вернуться к предложенной картинке, то грубо говоря, нужно уложить Лену горизонтально, головой, например, вправо, независимо от ее исходного положения, тем самым вписав в фиксированную геометрическую фигуру ее признаки (вы сами предложили этот пример) :).
Если фигура, в которую вписываются точки локальных признаков, — квадрат, то да, нужно будет делать 4 сравнения (а то и 8 для поддержки зеркального отражения картинки). Скорее всего количество сравнений можно уменьшить, договорившись, что центр масс всех вписанных точек должен оказаться, скажем, в первом квадранте квадрата.
Немного сумбурно описал, но надеюсь, сумел передать идею.
Используем промежуточную линковку (ld -r) объектников, сгруппированных по подсистемам. Правда, не в целях оптимизации производительности, а для шаманства с секциями (метки начала/конца, чексуммы и т.д.). Хотя оптимизация всего этого тоже интересна. :)
А вы не в курсе, GCC умеет такие штуки? Можно ли оптимизировать несколько объектников вместе, получив объединенный и релоцируемый (то есть как -Qipo-c, наверное)? Насколько я понимаю, -flto (аналог -Qipo?) годится только для whole program и создает исполняемый файл.
__class__, тип при этом действительно не меняется.Попробуйте в консоли:
Где
Py_TYPE(./Include/object.h):Все по-настоящему, успешное присваивание
__class__меняет тип объекта.