Обновить
4
0
Руслан@cheremin

Пользователь

Отправить сообщение
Этой модели памяти всего год. Насколько мне известно, идеально ее не реализует ни один из компиляторов. Плюс время на обкатку — в яве баги в реализации concurrency вылезают до сих пор — вы думаете, разработчики gcc как-то принципиально безошибочнее разработчиков явы?
Вы, возможно, удивитесь, но собственный «аллокатор» — точнее, пул объектов — можно написать и на яве. Некоторые так и делают, хотя мало кому удается действительно обойти по скорости родную аллокацию + new gen GC (ибо инженеры оракла далеко не дураки). Гарантии, которые дает С++ не выше — даже С-библиотека обращается, в конце-концов, к операционке, которая, в общем случае, realtime гарантий не дает, и все, что делает С-stdlib так это кэширует запрошенную у операционки память. Это ровно то же, что пул объектов в яве.

То, что описал автор — это ожидаемое поведение. В конце-концов, когда операционка выгружает ваше супер-пупер оптимизированное С++ приложение в своп — вы ж не начинаете утверждать, что С++ не подходит для приложений, требующих высокую отзывчивость? Я не знаю такой среды/фреймворка, который бы обеспечивал полную предсказуемость по всему стеку технологий начиная от железа. Все равно приходится засучивать рукава, а где-то и просто молиться великому Тьюрингу (только тссс об этом!).

Честный realtime требует сильного изменения подхода к разработке. Это окупается в довольно узких диапазонах бизнес-требований. Мягкий realtime, кмк, требует примерно одинаковой степени изобретательности, что на С, что на яве. Но ява сама по себе проще как язык, и разработчиков под нее больше — что немаловажно.

>потому что очень многое начинает зависеть от настроек GC, в тонкостях которых разобраться посложнее, чем с умными указателями или всякими RAII

Я сильно сомневаюсь, что разобраться с умными указателями в случае многопоточных приложений будет проще (и они будут эффективнее), чем с GC. Немаловажно и то, что изменения в настройках GC — это именно настройки. Изменения в политике использования памяти в С++ — это изменения в коде приложения. Со всем сопутствующим риском.
А в С++ уже появилась явная модель памяти? Не только специфицированная, но и корректно реализованная хотя бы парочкой-тройкой распространенных компиляторов хотя бы на 2-3 основных платформах? И отлаженная парой-тройкой лет активного использования сообществом? ;) — потому что у явы-то такая модель есть, и она в работе уже с 2005 года. Лет через 5 Сx0 и сможет по этому параметру с явой соперничать, вряд ли раньше

Главное отличие C от Java на мой взгляд — это вовсе не GC. Главное отличие, это то, что в java нет такого понятия, как undefined behavior. Результат выполнения любого кода можно предсказать по спецификации. Во многих случаях это ценнее, чем 10-20% прироста производительности, который дает хорошо написанный С++ код по сравнению с хорошо написанным java-кодом.
По-моему, как раз вполне прямая причинность: чтобы восстановить исходный «котон» нужно чтобы антиВасилий взаимодействовал с точной копией Василия. То есть нужна полная информация о Василии, позволяющая создать точную копию.
Нужно определить понятие перемещения во времени. В классике подразумевается, что в прошлое передается информация — именно это может приводить ко всевозможным парадоксам, связанным с нарушением причинности. Если информация не передается (а здесь именно так) то никаких парадоксов нет.
Что значит «аннигилировать», и чем ядерная реакция A+(-A) отличается от реакции A+(-B)? Какое свойство именно реакции аннигиляции важно в вашем рассуждении?
Информацию так можно переместить? Все парадоксы «путешествий во времени» связаны с нарушением причинности. Если информация не передается назад во времени, то причинность в безопасности.
А откуда здесь другие частицы? Все известные мне коты состоят из протонов, нейтронов и электронов.

Любые частицы _могут_ взаимодействовать с любыми. Какое принципиальное свойство именно аннигиляции важно в вашем примере?
Как-то не очень понятно, как вообще это связано с перемещениями во времени. Здесь есть передача информации о будущих событиях в прошлое?
>Любая атомарная операция является полным барьером (full memory barrier).

В таких обобщениях надо не забывать указывать архитектуру. Потому что да, на x86 'lock add %esp,0' используется как барьер — в яве volatile store как раз через него реализуется, хотя насчет полноты я не уверен, надо проверять. Но, например, на Azul VEGA (кажется) есть атомики без семантики барьера. Это не обязательно совершенно.
Еще раз повторяю — это не использование while является избыточным, это использование wait() является вторичным по отношению к busy-loop.

Просто сейчас молодым разработчикам сходу дают шаблон wait/notify, хотя стоило бы начать с busy-loop. Конечно, это оптимальнее с точки зрения способности молодняка сразу писать приемлемый код — а то напишет у вас джуниор межпоточный протокол на busy loop-ах, и будете вы думать, почему у вас загрузка сервера вдруг 800%. Но с точки зрения правильного понимания сути лучше было бы учить сначала циклу ожидания, а потом уже его оптимизации через wait/notify. Потому что иначе получается как раз то, о чем мы сейчас с вами беседуем — человек просто не видит из каких частей состоит конструкция, которую он использует, и как эти части эволюционно развивались к своему текущему состоянию. И поэтому не понимает что первично, а что вторично, а что вообще не обязательно.
>Ну что значит не причина.

Это значит, что это не причина, а лишь удобный пример, который проще объяснить молодым разработчикам, стремящимся побыстрее писать код, и не любящим вдаваться в концептуальные тонкости. Настоящая причина, повторяю, в том, что цикл проверки состояния — первичен, а wait() лишь способ его оптимизации.

>Ну вот в Джаве же не так?

Как — не так? В джаве точно так же надо оборачивать wait() в busy-loop. Если коллега-джавист воспринимает ложные просыпания как дикость, то он просто не в теме. Ну либо знает про настоящую причину — но это маловероятно, я крайне редко встречаю людей, которые идут дальше «ложных просыпаний».

>Это к вопросу о том, что в теории для wait-а не требуется цикл, а добавлять его приходится из чисто практических соображений (особенности ядра). Скажем так, в псевдокоде он бы не потребовался.

Как раз наоборот — в псевдокоде, реализующем лишь логику, как раз wait() не понадобился бы. Достаточно было бы busy loop. Псевдокод обычно не содержит технических оптимизаций.
Внезапные пробуждения — это вовсе не _причина_ чтобы оборачивать wait в while. Причина — это то, что ожидание извещения и ожидание состояния — это идейно разные вещи. Просто ожидать наступления определенного состояния — это делается busy loop-ом на соответствующих переменных. Но поскольку это жжет циклы процессора без особого толку, то для большинства практических случаев, где не нужно ultra-low-latency, хочется как-то эти циклы более полезно использовать. И тут возникает такой инструмент как wait(), который позволяет сказать системному планировщику задач, что вот ближайшие сколько-то времени этому потоку процессор не нужен.

Вот только исходный busy-loop от этого никуда не девается — просто внутрь него добавляется этот самый wait. То есть это не wait оборачивается в цикл — это цикл дополняется wait-ом.

А внезапные пробуждения — это всего лишь наглядный пример, с помощью которого эту идею удобно объяснять новичкам. И, кстати, вы уверены, что вы именно на нее нарвались, а не на какой-то неучтенный notify? Потому что сама проблема вроде бы воспроизводилась очень редко даже когда этот баг в ядре был, а уж сейчас и вообще почти не воспроизводится.
А там описано почему выбрана именно такая политика? (Страшновато за него браться)
О, вот это интересно — т.е. уже не полностью inclusive? Есть исключения?
Кэши у интела inclusive — то, что есть в L1 обязательно должно быть и в L2 и в L3. en.wikipedia.org/wiki/CPU_cache#Exclusive_versus_inclusive
>Это я к тому, что по количеству обработанных задач в некий промежуток времени можно оценить накладные расходы более точно, чем считать время в каждом таске.

Какие именно накладные расходы можно оценить «более точно»? Чтобы говорить то, что вы говорите, нужно иметь в голове некую модель того, что происходит в системе, и что именно мы хотим измерить. Измеряя количество задач в единицу времени вы тестируете систему в режиме насыщения — в этом режиме вы можете упираться в совершенно другие узкие места, чем упирается время ответа в ненасыщенном режиме.

Например, в ненасыщенном режиме у вас «стоимость» CAS-а (внутри put)будет примерно постоянной, потому что contention будет низким, и вероятность неуспешного CAS-а тоже будет низкой. Зато будет важна эффективность работы CardMarking-а — потому что в среднем таблица будет едва-едва заполненной, и сканировать ее целиком каждый раз будет не эффективно, и CardMarking тут очень к месту.

А в режиме насыщения CardMarking будет только мешаться, потому что таблица скорее всего будет заполнена, и толку от CardMarking будет мало, а накладные расходы он вносит. С другой стороны, стоимость CAS-а вырастет (поскольку вырастет contention), и начнет заметно зависеть от всяких деталей компоновки объектов в памяти, и количество неуспешных CAS-ов тоже вырастет, и поэтому будет важна реализация backoff после неудавшихся CAS-ов.

Это так, навскидку описание. Все может быть иначе, и вообще дракон из монитора вылезет и откусит экспериментатору голову.

Для нашей конкретной задачи нужно минимальное время доставки до обработчика каждого конкретного таска. Предельная пропускная способность нас интересует гораздо меньше — у нас система даже близко не подходит к режиму насыщения. Поэтому эксперимент поставлен так, как поставлен. «Плюнь тому в глаза, кто скажет, что можно обнять необъятное!»
>Надежней мерять не маленькие интервалы и суммировать, а нагрузить приложение реальными данными (или похожими на реальные) и измерять количество данных в час/день/неделю — эта величина коррелирует с latency.

Надежнее всего, на мой взгляд, не думать, что есть единственно правильный метод измерять что либо. Есть безусловно место и для синтетических бенчмарков всего приложения в целом. Но если вы не представляете что происходит в вашей системе на уровне отдельных ее компонент, вы не сможете правильно интерпретировать эти синтетические бенчмарки. Что толку знать, что у вас полное время ответа 100мс, когда надо 20, а вы не представляете как можно хотя бы один из этапов обработки сделать быстрее 50мс, потому что складывая пасьянс из стандартных примитивов быстрее не сделать?

>Что дает измерение задержки executor-а на маленьких тасках?

Интерпретация _любых_ бенчмарков требует аккуратности и опыта. Ложные выводы можно сделать как из результатов синтетических тестов «в $$$», так и из результатов микробенчмарков в микросекундах.
Это что, вызов на дуэль по боям в грязи?

Если честно, я не большой фанат «жестких» стандартов. На мой взгляд демонстрировать способы решения задач полезнее, чем давать готовые решения. Но я понимаю, что это подходит не для всех
Это когда producer лениво и неторопливо генерирует 10 сообщений в минуту — тогда да, тогда он производитель. А когда 10 000 в секунду — тогда какой же он производитель, разве можно что-то с такой скоростью производить? — Он нагребатор, как есть нагребатор.

В данном примере нагребатор/разгребатор гораздо более яркая и говорящая метафора. И не только в данном, как показывает практика )

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Дата рождения
Зарегистрирован
Активность