Std a что обозначает
Маркировка поршней
Калькулятор расчета рабочего объёма двигателя внутреннего сгорания
Калькулятор для расчета рабочего объема цилиндров двигателя автомобиля
Маркировка поршней позволяет судить не только об их геометрических размерах, но и материале изготовления, технологии производства, допустимом монтажном зазоре, товарном знаке производителя, направлении установки и многом другом. В связи с тем, что в продаже встречаются поршни как отечественного, так и импортного производства, то автовладельцы порой сталкиваются с проблемой расшифровки тех или иных обозначений. В данном материале собран максимум информации, позволяющий получить сведения об маркировке на поршне и разобраться что значат цифры, буквы и стрелки.
1 — Обозначение торговой марки, под который выпущен поршень. 2 — Заводской номер изделия. 3 — Диаметр увеличен на 0,5 мм, то есть, в данном случае это ремонтный поршень. 4 — Значение наружного диаметра поршня, в мм. 5 — Значение теплового зазора. В данном случае он равен 0,05 мм. 6 — Стрелка, указывающая направление установки поршня в сторону движения машины. 7 — Техническая информация производителя (необходима при обработке двигателя).
Информация на поверхности поршня
Обсуждения вопроса о том, что означает маркировка на поршнях стоит начать с того, какую вообще информацию производитель наносит на изделие.
Кроме этих обозначений также существуют и другие, и они могут отличаться у разных производителей.
Где расположена маркировка поршня
Многих автолюбителей интересует ответ на вопрос, где находится маркировка поршней. Это зависит от двух обстоятельств — стандартов конкретного производителя и той или иной информации о поршне. Так, основные сведения печатаются на его нижней части («лицевой» стороне), на ступице в районе отверстия под поршневой палец, на весовой бобышке.
Маркировка поршней ВАЗ
По статистике, маркировкой ремонтных поршней чаще всего интересуются владельцы или мастера по ремонту двигателей автомобилей ВАЗ. Далее приведем информацию по различным поршням.
ВАЗ 2110
Для примера возьмем двигатель автомобиля ВАЗ-2110. Чаще всего в данной модели используются поршни с маркировкой 1004015. Изделие производится непосредственно на ОАО «АвтоВАЗ». Краткая техническая информация:
Непосредственно на корпусе поршня может быть нанесена дополнительная информация. Например:
Основные маркировочные символы, наносимые на днище поршня:
Для ремонтных поршней ВАЗ также существуют два отдельных обозначения:
Обратите внимание, что для различных марок машин (в том числе для разных двигателей) значение отличия ремонтных поршней нужно смотреть в справочной информации.
ВАЗ 21083
Другим популярным «ВАЗовским» поршнем является 21083-1004015. Он также производится на ОАО АвтоВАЗ. Его технические размеры и параметры:
Он имеет аналогичные обозначения, что и ВАЗ 2110-1004015. Остановимся немного подробнее на классе поршня по наружному диаметру и классе отверстия под поршневой палец. Соответствующая информация сведена в таблицы.
Класс поршня по наружному диаметру | A | B | C | D | E |
---|---|---|---|---|---|
Диаметр поршня 82,0 (мм) | 81,965-81,975 | 81,975-81,985 | 81,985-81,995 | 81,995-82,005 | 82,005-82,015 |
Диаметр поршня 82,4 (мм) | 82,365-82,375 | 82,375-82,385 | 82,385-82,395 | 82,395-82,405 | 82,405-82,415 |
Диаметр поршня 82,8 (мм) | 82,765-82,775 | 82,775-82,785 | 82,785-82,795 | 82,795-82,805 | 82,805-82,815 |
Интересно, что модели поршней ВАЗ 11194 и ВАЗ 21126 выпускаются только в трех классах — A, B и C. При этом размер шага соответствует 0,01 мм.
Таблица соответствия моделей поршня и моделей двигателя (марки) автомобилей ВАЗ.
Модель двигателя ВАЗ | Модель поршня | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2101 | 21011 | 2105 | 21213 | 2123 | 2108 | 21083 | 2110 | 2112 | 21124 | 21126 | 21128 | 11194 | |
2101 | |||||||||||||
21011 | |||||||||||||
2103 | |||||||||||||
2104 | |||||||||||||
2105 | |||||||||||||
2106 | |||||||||||||
21073 | |||||||||||||
2121 | |||||||||||||
21213 | |||||||||||||
21214 | |||||||||||||
2123 | |||||||||||||
2130 | |||||||||||||
2108 | |||||||||||||
21081 | |||||||||||||
21083 | |||||||||||||
2110 | |||||||||||||
2111 | |||||||||||||
21114 | |||||||||||||
11183 | |||||||||||||
2112 | |||||||||||||
21124 | |||||||||||||
21126 | |||||||||||||
21128 | |||||||||||||
11194 |
Отверстия под поршневой палец:
Класс отверстия под поршневой палец | 1 | 2 | 3 |
---|---|---|---|
Диаметр отверстия под поршневой палец(мм) | 21,982-21,986 | 21,986-21,990 | 21,990-21,994 |
Маркировка поршней ЗМЗ
Другая категория автовладельцев, интересующихся маркировкой поршней, имеет в своем распоряжении моторы марки ЗМЗ. Они устанавливаются на автомобили ГАЗ — Волга, Газель, Соболь и другие. Рассмотрим обозначения, имеющиеся на их корпусах.
Обозначение «406» означает, что поршень предназначен для установки в двигатель ЗМЗ-406. На днище поршня выбито два обозначения. По букве, нанесенной краской, на новом блоке поршень подбирается к цилиндру. При ремонте с расточкой цилиндров требуемые зазоры выполняются в процессе расточки и хонингования под заранее приобретенные поршни с нужным размером.
Римская цифра на поршне указывает на нужную группу поршневого пальца. Диаметры отверстий в бобышках поршня, головке шатуна, а также наружные диаметры поршневого пальца делятся на четыре группы, помеченные краской: I — белая, II — зеленая, III — желтая, IV — красная. На пальцах номер группы также обозначен краской на внутренней поверхности или на торцах. Он должен совпадать с группой, указанной на поршне.
Непосредственно на шатуне номер группы аналогично должен отмечаться краской. При этом упомянутый номер должен или совпадать или находиться рядом с номером группы пальца. Такой подбор обеспечивает ситуацию, когда смазанный палец с небольшим усилием перемещается в головке шатуна, однако не выпадает из него. В отличие от поршней ВАЗ, где направление указывается стрелкой, на поршнях ЗМЗ производитель прямо пишет слово «ПЕРЕД» или просто ставит букву «П». При сборке выступ на нижней головке шатуна должен совпадать с этой надписью (быть на той же стороне).
Существует пять групп, с шагом, равным 0,012 мм, которые обозначаются буквами А, Б, В, Г, Д. Эти размерные группы выбираются по наружному диаметру юбки. Они соответствуют:
Значение группы поршня клеймится на его днище. Так, существуют четыре размерные группы, которые маркируются краской на бобышках поршня:
Метки группы отверстия под палец могут также быть нанесены на днище поршня римскими цифрами, при этом каждой цифре соответствует свой цвет (I — белый, II — зеленый, III — желтый, IV — красный). Размерные группы подобранных поршней и поршневых пальцев должны совпадать.
Двигатель ЗМЗ-405 устанавливается на автомобиль ГАЗ-3302 «Газель Бизнес» и ГАЗ-2752 «Соболь». Расчетный зазор между юбкой поршня и цилиндром (для новых деталей) должен быть равен 0,024…0,048 мм. Он определяется как разность размеров минимального диаметра цилиндра и максимального диаметра юбки поршня. Существует пять групп, с шагом, равным 0,012 мм, которые обозначаются буквами А, Б, В, Г, Д. Эти размерные группы выбираются по наружному диаметру юбки. Они соответствуют:
Значение группы поршня клеймится на его днище. Так, существуют четыре размерные группы, которые маркируются краской на бобышках поршня:
Таким образом, если на поршне двигателя ГАЗ стоит, например, буква В, то это означает, что двигатель дважды подвергался капитальному ремонту.
В ЗМЗ 409 почти все размеры те же, что и в ЗМЗ 405, за исключением выемки (лужа), она глубже, чем в 405. Делается это для компенсации степени сжатия, размер h увеличивается на поршнях 409. Также компрессионная высота у 409 равна 34 мм, а у 405 — 38 мм.
Приведем аналогичную информацию для двигателя марки ЗМЗ 402.
Надпись «Селективный подбор» на поршнях
Обратите внимание, что с октября 2005 года на поршнях 53, 523, 524 (устанавливаются в том числе на многие модели двигателей ЗМЗ), на их днище устанавливается печать «Селективный подбор». Такие поршни изготавливаются по более прогрессивной технологии, о которой отдельно написано в технической документации к ним.
Марка поршня ЗМЗ | Нанесенное обозначение | Куда нанесено обозначение | Метод нанесения надписи |
---|---|---|---|
53-1004015-22; «523.1004015»; «524.1004015»; «410.1004014». | Товарный знак ЗМЗ | На ступице в районе отверстия под поршневой палец | Литье |
Обозначение модели поршня | На ступице в районе отверстия под поршневой палец | Литье | |
«Перед» | На ступице в районе отверстия под поршневой палец | Литье | |
Маркировка диаметра поршня А, Б, В, Г, Д. | На днище поршня | Травление | |
Клеймо БТК | На днище поршня | Краской | |
Маркировка диаметра под палец (белый, зеленый, желтый) | На весовой бобышке | Краской |
Аналогичная информация поршня 406.1004015:
Марка поршня ЗМЗ | Нанесенное обозначение | Куда нанесено обозначение | Метод нанесения надписи |
---|---|---|---|
4061004015; «405.1004015»; «4061.1004015»; «409.1004015». | Товарный знак ЗМЗ | На ступице в районе отверстия под поршневой палец | Литье |
«Перед» | |||
Модель «406, 405, 4061,409» (406- АР; 406-БР) | |||
Маркировка диаметра поршня А, Б, В, Г, Д | На днище поршня | Ударный | |
Маркировка диаметра под палец (белый, зеленый, желтый, красный) | На весовой бобышке | Краской | |
Материал изготовления «АК12ММгН» | В районе отверстия под поршневой палец | Литье | |
Клеймо БТК | На днище поршня | Травлением |
Маркировка поршней «Тойота»
У поршней на двигателях «Тойота» также имеются свои обозначения и размеры. Например, на популярной машине Land Cruiser поршни обозначаются английскими буквами A, B и C, а также числами от 1 до 3. Соответственно, буквы означают размер отверстия под поршневой палец, а цифры — размер диаметра поршня в районе «юбки». Ремонтный поршень имеет +0,5 мм по сравнению к стандартному по диаметру. То есть, у ремонтных меняются лишь обозначения букв.
Обратите внимание, что при покупке бывшего в употреблении поршня необходимо замерить тепловой зазор между юбкой поршня и стенкой цилиндра. Он должен находиться в пределах 0,04…0,06 мм. В противном случае необходимо проводить дополнительную диагностику двигателя и при необходимости выполнять ремонт.
Поршни завода «Мотордеталь»
На многих отечественных и импортных машинах используются ремонтные поршни, изготовленные на производственных мощностях костромского производителя поршневых групп «Мотордеталь-Кострома». Данное предприятие выпускает поршни с диаметром от 76 до 150 мм. На сегодняшний день производятся такие типы поршней:
Поршни, выпущенные под указанной торговой маркой, имеют собственные обозначения. При этом информация (маркировка) может быть нанесена двумя способами — лазером и микроударом. Для начала рассмотрим на конкретных примерах маркировку, сделанную при помощи лазерной гравировки:
Теперь рассмотрим обозначения, нанесенные при помощи так называемого микроудара, на конкретных примерах:
Здесь же стоит отметить, что для производства разных поршней используются различные же алюминиевые сплавы с легирующими добавками. Однако эта информация не указывается прямо на корпусе поршня, но записывается в его технической документации.
Поршень размер std что это
Что за поршни.
Что за поршни.
Hells » Вт, 17 май 2011 16:28
Re: Что за поршни.
viktor_l » Вт, 17 май 2011 16:36
Re: Что за поршни.
Hells » Вт, 17 май 2011 16:39
Re: Что за поршни.
viktor_l » Вт, 17 май 2011 16:43
Re: Что за поршни.
Юрка » Вт, 17 май 2011 17:35
Re: Что за поршни.
Hells » Вт, 17 май 2011 17:41
Re: Что за поршни.
granda » Вт, 17 май 2011 18:39
Re: Что за поршни.
Hells » Вт, 17 май 2011 18:58
Re: Что за поршни.
granda » Вт, 17 май 2011 19:06
Re: Что за поршни.
Юрка » Вт, 17 май 2011 19:11
Re: Что за поршни.
Hells » Вт, 17 май 2011 19:16
Re: Что за поршни.
Hells » Вт, 17 май 2011 19:17
Re: Что за поршни.
vnizam » Вт, 17 май 2011 19:53
Что самое главное в Mitsubishi.
Правильно!,самое главное в Mitsubishi не обосраться.
Re: Что за поршни.
Boatswain » Вт, 17 май 2011 21:42
Re: Что за поршни.
Hells » Вт, 17 май 2011 23:00
Источник статьи: http://cedia-club.ru/forum/viewtopic.php?t=11407
Поршень размер std что это
Сообщение SG74 » 12 авг 2014, 22:31
Сообщение Коротыш » 13 авг 2014, 09:01
Сообщение SG74 » 13 авг 2014, 09:19
Сообщение Коротыш » 13 авг 2014, 09:33
Сообщение hc2hunter » 13 авг 2014, 09:59
По-моему, А ставится на заводе.
В-первый ремонтный комплект.
С-последний ремонтный комплект (потом двигатель на свалку).
Вроде и с кольцами так. А что привезти разницы никакой нет. Просто смотрите нормальных поставщиков и сроки по Автодоку или Экзисту.
А про разные размеры с завода — по-моему это чушь. Современные фрезеры делают с такой точностью, что поршня подбирать не надо, сразу все четко до сотки. Просто при ремонте делаются контрольные замеры и заказываются нужные поршни и кольца (которые отличаются обычно по 5 соток друг от друга).
Если не прав — поправьте.
Источник статьи: http://www.pajero4x4.ru/bbs/phpBB2/viewtopic.php?t=130503
Поршневые кольца, размер STD KOLBENSCHMIDT 3 вида (арт. 800001011000, 800001012000, 800001013000 какая разница?)
sergio3538
Участник
☭ Рождённый в СССР ☭
Советчиков много, но решать тебе и разбираться тоже тебе.
Параметры
Высота, мм 1.75
Диаметр, мм 81
Поверхность фосфорированный
Толщина, мм 3.55
Параметры
Высота, мм 3
Диаметр, мм 81
Поверхность фосфорированный
хромированный
Толщина, мм 3.65
kudrik
Раздаю советы, бездвоздмездно
—Это же повторить для всех двух других артикулов 2 и 3.
Распечатать всё в ворд/блокнот. И внимательно читать параметры всех 9-ти колец.
—Артикулы 2 и 3 имеют одинаковые:
верхнее кольцо — хром
среднее кольцо — чугун
НО! разное м/с кольцо(по конструкции)
в артикуле 800001013000 кольцо наборное, элементы стальные
Источник статьи: http://vwts.ru/forum/topic/240737/
Поршни размерами (89—)
Каталог Teikin дает исчерпывающую информацию: характеристики колец, пальца, стопора, формы поверхности. Чтобы разобраться во всех аббревиатурах. Прочтите информацию-расшифровку ниже.
Оригинальное описание параметров, посмотреть
Источник статьи: http://size.name/catalog/porshnya?d_out=89
Поршни размерами (71—)
Каталог Teikin дает исчерпывающую информацию: характеристики колец, пальца, стопора, формы поверхности. Чтобы разобраться во всех аббревиатурах. Прочтите информацию-расшифровку ниже.
Оригинальное описание параметров, посмотреть
Источник статьи: http://size.name/catalog/porshnya?d_out=71
Поршни размерами (78—)
Каталог Teikin дает исчерпывающую информацию: характеристики колец, пальца, стопора, формы поверхности. Чтобы разобраться во всех аббревиатурах. Прочтите информацию-расшифровку ниже.
Оригинальное описание параметров, посмотреть
Восемь возможностей C++17, которые должен применять каждый разработчик
Мы поговорим о восьми удобных изменениях, которые влияют на ваш повседневный код. Четыре изменения касаются самого языка, а ещё четыре — его стандартной библиотеки.
Благодарности
Некоторые примеры я брал из докладов на конференциях Russian C++ User Group — за это огромное спасибо её организаторам и докладчикам! Я брал примеры из:
1. Декомпозиция при объявлении (англ. structural bindings)
В C++17 есть ограничения декомпозиции при объявлении:
Пример декомпозиции с try_emplace и декомпозиции key-value при обходе map:
2. Автоматический вывод параметров шаблонов
Вы можете создавать свои подсказки для автоматического вывода параметров шаблона: см. Automatic_deduction_guides
Интересная особенность: конструктор из initializer_list<> пропускается для списка из одного элемента. Для некоторых JSON библиотек (таких как json_spirit) это может оказаться фатальным. Не играйтесь с рекурсивными типами и контейнерами STL!
3. Объявление вложенных пространств имён
Избегайте вложенности пространств имён, а если не избежать, то объявляйте их так:
4. Атрибуты nodiscard, fallthrough, maybe_unused
Более подробно об атрибутах рассказано в статье Как пользоваться атрибутами из C++17. Здесь будут краткие выдержки.
В C++ приходится добавлять break после каждого case в конструкции switch, и об этом легко забыть даже опытному разработчику. На помощь приходит атрибут fallthrough, который можно приклеить к пустой инструкции. Фактически атрибут приклеивается к case, следующему за пустой инструкцией.
В проектах с высокими требованиями к производительности могут практиковать отказ от выброса исключений (по крайней мере в некоторых компонентах). В таких случаях об ошибке выполнения операции сообщает код возврата, возвращённый из функции. Однако, очень легко забыть проверить этот код.
Если вы используете, например, свой класс ошибок, то вы можете указать атрибут единожды в его объявлении.
Иногда программисты создают переменную, используемую только в отладочной версии для хранения кода ошибки вызванной функции. Возможно, это просто ошибка дизайна кода, и возвращаемое значение следовало обрабатывать всегда. Тем не менее:
5. Класс string_view для параметров-строк
Подробнее о том, почему string_view лучше всего применять только для параметров, читайте в статье std::string_view конструируется из временных экземпляров строк
Класс string_view хорош тем, что он легко конструируется и из std::string и из const char* без дополнительного выделения памяти. А ещё он имеет поддержку constexpr и повторяет интерфейс std::string. Но есть минус: для string_view не гарантируется наличие нулевого символа на конце.
6. Классы optional и variant
Применение optional<> и variant<> настолько широко, что я даже не буду пытаться полностью описать их в этой статье. Ключевые правила:
Пример кода с optional:
Пример кода с variant: здесь мы используем variant для хранения одного из нескольких состояний в случае, когда разные состояния могут иметь разные данные
Преимущество variant в его подходе к управлению памяти: данные хранятся в полях значения типа variant без дополнительных выделений памяти. Это делает размер типа variant зависимым от типов, входящих в его состав. Так может выглядеть таблица размеров на 32-битных процессорах (но это неточно):
7. Используйте функции std::size, std::data, std::begin, std::end
Может быть, для манипуляций с байтами лучше опираться на библиотеку GSL (C++ Core Guidelines Support Library).
8. Используйте std::filesystem
Чем плох boost::filesystem? Оказывается, у него есть несколько проблем дизайна:
Любой опытный программист знает о разнице в обработке путей между Windows и UNIX-системами:
Конечно же filesystem абстрагируется от подобных различий и позволяет легко работать как с платформо-зависимыми строками, так и с универсальным UTF-8:
Бонусное правило: прекратите переизобретать clamp, int_to_string и string_to_int
Функция std::clamp дополняет функции min и max. Она обрезает значение и сверху, и снизу. Аналогичная функция boost::clamp доступна в более ранних версиях C++.
Правило «не переизобретайте clamp» можно обобщить: в любом крупном проекте избегайте дублирования маленьких функции и выражений для округлений, обрезаний значений и т.п. — просто один раз добавьте это в свою библиотеку.
Аналогичное правило работает для задач обработки строк. У вас есть своя маленькая библиотека для строк и парсинга? В ней есть парсинг или форматирование чисел? Если есть, замените свою реализацию на вызовы to_chars и from_chars
Функции to_chars и from_chars поддерживают обработку ошибок. Они возвращают по два значения:
Поскольку в прикладном коде способ реакции на ошибку может различаться, следует помещать вызовы to_chars и from_chars внутрь своих библиотек и утилитных классов.
Ускоряем std::shared_mutex в 10 раз
В этой статье мы детально разберем атомарные операции и барьеры памяти C++11 и генерируемые ими ассемблерные инструкции на процессорах x86_64.
Далее мы покажем как ускорить работу contfree_safe_ptr до уровня сложных и оптимизированных lock-free структур данных аналогичных по функциональности std::map<>, например: SkipListMap и BronsonAVLTreeMap из библиотеки libCDS (Concurrent Data Structures library): github.com/khizmax/libcds
1000%, поэтому мы не будем уделять внимание слабым и сомнительным оптимизациям.
Три связанные статьи:
Высокая производительность lock-based структур данных
По порядку покажем, как реализовать собственный высокопроизводительный contention-free shared-mutex, почти не конфликтующий на чтениях. Реализуем собственные активные блокировки spinlock и recursive-spinlock для блокирования строк(rows) на операциях update. Создадим RAII-блокирующие указатели, чтобы избежать затрат на многократное блокирование. Приведем результаты тестов производительности.
И как бонус «Just for fun» покажем, как реализовывать собственный класс упрощенного секционированного типа partitioned_map, ещё сильнее оптимизированного для многопоточности, состоящего из нескольких std::map, по аналогии с partition table из СУБД, когда границы каждой секции известны изначально.
Основы атомарных операций
Рассмотрим основы многопоточности, атомарности и барьеров памяти. Если мы изменяем одни и те же данные из множества потоков, т.е. если мы запускаем функцию thread_func() одновременно в нескольких потоках:
То каждый поток вызывающий функцию thread_func() прибавляет 1 к обычной разделяемой переменной int a; Такой код в общем случае не будет потоко-безопасным, т.к. составные операции (RMW – read-modify-write) над обычными переменными состоят из множества маленьких операций, между которыми другой поток может изменить данные. Операция a = a+1; состоит как минимум из трех мини-операций:
Два потока прибавили к одной и той же глобальной переменной +1, но в итоге переменная a = 1 – такая проблема называется Data-Races.
Чтобы избежать этой проблемы есть 3 способа:
Следующее всегда будет происходить на процессорах x86_64 (пошагово):
На процессорах с поддержкой LL/SC, например на ARM или PowerPC, будут происходить другие шаги, но с тем же самым результатом a = 2.
Функции члены шаблона класса std::atomic<> всегда являются атомарными.
Приведем 3 фрагмента правильного кода идентичного по смыслу:
2. Это идентично примеру 1:
3. И это идентично примеру 1:
Это значит, что для std::atomic:
Здесь есть 5 основных отличий:
[19] coliru.stacked-crooked.com/a/1ec9a0c2b10ce864
Смысл барьеров очень простой – оптимизатору компилятора запрещается перемещать инструкции из критической секции наружу:
Например, строка int new_shared_value = shared_value; может быть выполнена до lock_flag.clear(std::memory_order_release);. Такое переупорядочивание приемлемо и не создает data-races, т.к. весь код, который обращается к общим для множества потоков данным всегда заключен внутри двух барьеров acquire и release. А снаружи находится код, который работает только с локальными для потока данными – и не важно в каком порядке он будет выполнен. Локальные для потока зависимости всегда сохраняются также, как это происходит при однопоточном исполнении, поэтому int new_shared_value = shared_value; не может быть выполнен раньше, чем shared_value += 25;
Так что же запрещают, и что разрешают барьеры acquire-release для критической секции:
А теперь давайте посмотрим, чтобы получилось, если бы мы использовали Relaxed-Release семантику вместо Acquire-Release:
Покажем схематически этот процесс во времени:
Например, два потока начинают исполнение функции add_to_shared().
Чтобы было удобно использовать, его можно объединить в один класс:
Чтобы вы всегда могли понять какой барьер вам использовать и во что он будет скомпилирован на x86_64, я приведу следующую таблицу:
Следующее необходимо знать, только если вы пишете код на ассемблере x86_64, когда компилятор не может менять местами ассемблерные инструкции для оптимизации:
• На x86_64 не могут быть переупорядочены любые:
Посмотреть реальный ассемблерный код для разных компиляторов вы можете по ссылкам. А также можете выбрать другие архитектуры: ARM, ARM64, AVR, PowerPC.
GCC 6.1 (x86_64):
Примеры переупорядочивания операций обращения к памяти
Теперь покажем более сложные примеры переупорядочивания из 4-ех последовательных операций: LOAD, STORE, LOAD, STORE.
1-й случай – интересен тем, что несколько критических секций могут быть слиты (fused) в одну.
Компилятор не может выполнить такое переупорядочение во время компиляции, но компилятор позволяет процессору выполнять это переупорядочение во время выполнения. Поэтому слияние критических секций, которые выполняются в разных последовательностях в разных потоках, не может привести к взаимной блокировке (deadlock), потому что начальная последовательность инструкций будет видна процессору. Следовательно процессор попытается заранее войти в вторую критическую секцию, но если он не сможет, он продолжит выполнение первой критической секции и после полного её завершения будет ожидать входа во вторую критическую секцию.
3-й случай – интересен тем, что части двух составных атомарных инструкций могут быть переупорядочены: STORE A LOAD B.
2й случай – интересен тем, что std::memory_order_seq_cst самый сильный барьер и казалось бы должен запрещать любые переупорядочивания атомарных или неатомарных операций вокруг себя. Но seq_cst-барьеры обладают только одним дополнительным свойством по сравнению с acquire/release – только если обе атомарные операции имеют seq_cst-барьер, то последовательность операций STORE-A(seq_cst); LOAD-B(seq_cst); не может быть переупорядочена. Приведем 2 цитаты стандарта C++:
There shall be a single total order S on all memory_order_seq_cst operations, consistent with the “happens before” order and modification orders for all affected locations, such that each memory_order_seq_cst operation B that loads a value from an atomic object M observes one of the following values: …
[ Note: memory_order_seq_cst ensures sequential consistency only for a program that is free of data races and uses exclusively memory_order_seq_cst operations. Any use of weaker ordering will invalidate this guarantee unless extreme care is used. In particular, memory_order_seq_cst fences ensure a total order only for the fences themselves. Fences cannot, in general, be used to restore sequential consistency for atomic operations with weaker ordering specifications. — end note ]
Разрешенные направления для переупорядочивания не–seq_cst операций (атомарных и неатомарных) вокруг seq_cst-операций такие же как и у acquire/release:
Возможны следующие изменения порядка вокруг memory_order_seq_cst–операций:
STORE-A(seq_cst); STORE-C(relaxed); LOAD-C(relaxed); ==>
LOAD-C(relaxed); STORE-C(relaxed); STORE-A(seq_cst);
т.к. на архитектуре PowerPC, для seq_cst-барьера добавляется sync (hwsync) только до STORE(seq_cst) и до LOAD(seq_cst), таким образом все инструкции которые находятся между STORE(seq_cst) и LOAD(seq_cst) могут исполниться до STORE(seq_cst): godbolt.org/g/dm7tWd
Покажем подробнее на примере из 3 переменных с семантикой: seq_cst и relaxed.
Какие переупорядочивания позволяет делать компилятор C++
Какие переупорядочивания позволяют делать разные CPU при отсутствии assembler-барьеров между инструкциями. “Memory Barriers: a Hardware View for Software Hackers” Paul E. McKenney June 7, 2010 — Table 5: www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
Есть ещё одна особенность обмена данными между потоками, которая проявляется при взаимодействии 4 потоков или больше. Если хотя бы в одной из следующих операций не используется самый строгий барьер memory_order_seq_cst, то разные потоки могут видеть одни и те же изменения в разном порядке. Например:
На рисунке покажем какое изменение порядка возможно для Acquire-Release семантики и для Sequential семантики на примере из 4-ех потоков:
Активные блокировки spin-lock и recursive-spin-lock
Покажем, как применяются барьеры памяти для атомарных операций на примере реализации собственных активных блокировок: spin-lock и recursive-spin-lock.
Далее эти блокировки понадобятся нам для блокирования отдельных строк (row-lock) таблицы вместо блокирования всей таблицы (table-lock) – это повысит степень параллелизма и увеличит производительность, т.к. разные потоки смогут работать с разными строками параллельно, не блокируя всю таблицу.
Количество объектов синхронизации предоставляемых операционной системой может быть ограничено. Количество строк в таблице может составлять миллионы или миллиарды, многие ОС не позволяют создавать столько мьютексов. А количество spin-lock может быть любым, насколько это позволяет оперативная память – поэтому их можно использовать для блокирования каждой отдельной строки.
Фактически spinlock – это + 1 байт к каждой строке(row), или +17 байт при использовании recursive-spin-lock (1 байт для флага + 8 байт для счетчика рекурсии + 8 байт для номера потока thread_id).
Пример вложенных блокировок:
Если вы попробуете запустить этот код в компиляторе MSVC 2013, то получите очень сильное замедление из-за функции std::this_thread::get_id(): connect.microsoft.com/VisualStudio/feedback/details/1558211
Мы доработаем класс recursive_spinlock_t, чтобы он кешировал thread-id в переменной __declspec(thread) – это аналог thread_local из стандарта С++11. Этот пример покажет хорошую производительность и в MSVC 2013: [28] coliru.stacked-crooked.com/a/3090a9778d02f6ea
Это временное исправление для старого MSVC 2013, поэтому о красоте такого решения мы думать не будем.
Считается, что в большинстве случаев повторное (рекурсивное) блокирование мьютекса – это ошибка проектирования, но в нашем случае это может быть медленным, но рабочим кодом. Во-вторых все ошибаются, и при вложенных рекурсивных блокировках recursive_spinlock_t отработает заметно медленнее, а spinlock_t зависнет навечно – что лучше, решать вам.
В случае использования нашего потоко-безопасного указателся safe_ptr – оба примера вполне логичны, но второй сработает только с recursive_spinlock:
Реализация собственного высокопроизводительного shared-mutex
Как известно, в новых стандартах C++ постепенно появлялись новые типы мьютексов: en.cppreference.com/w/cpp/thread
shared_mutex – это мьютекс позволяющий одновременно многим потокам читать одни и те же данные, если в этот момент нет потоков изменяющих эти данные. shared_mutex появился не сразу, т.к. шли споры о его производительности по сравнению с обычным std::mutex.
Классическая реализация shared_mutex со счетчиком количества читателей показывала преимущество в скорости только когда много читателей долго удерживали блокировку – т.е. когда долго читали. При коротких чтениях shared_mutex только замедлял программу и усложнял код.
Но все ли реализации shared_mutex столь медленные при коротких чтениях?
Основная причина медленной работы std::shared_mutex и boost::shared_mutex – это атомарный счетчик читателей. Каждый читающий поток инкрементирует счетчик при блокировании и декрементирует его при разблокировании. В итоге потоки постоянно гоняют между ядрами одну кэш-линию (а именно гоняют её exclusive-state (E)). По логике такой реализации, каждый читающий поток подсчитывает сколько всего сейчас читателей, но это абсолютно не важно для читающего потока, т.к. ему важно только, чтобы не было ни одного писателя. Причем, т.к. инкремент и декремент – это RMW операции, то они всегда генерируют очистку Store-Buffer (MFENCE x86_64) и на уровне x86_64 asm фактически соответствуют самой медленной семантике Sequential Consistency.
Попробуем решить эту проблему.
Есть тип алгоритмов, который классифицируется как write contention-free – когда нет ни одной ячейки памяти в которую могли бы писать более одного потока. А в более общем случае – нет ни одной кэш-линии в которую могли бы писать более одного потока. Чтобы наш shared-mutex при наличии только читателей классифицировался как write contention-free, необходимо, чтобы читатели не мешали друг другу – т.е. чтобы каждый читатель писал флаг (что он читает) в свою отдельную ячейку, и снимал флаг в этой же ячейке по окончанию чтения – без RMW-операций.
Write contention-free – это максимально производительная гарантия, которая производительнее, чем wait-free и чем lock-free.
Возможно, чтобы каждая ячейка располагалась в отдельной кэш-линии для исключения false-sharing, а возможно, и чтобы ячейки лежали плотно по 16 в одной кэш-линии – потеря производительности будет зависеть от CPU и количества потоков. Для устранения false-sharing — каждую переменную следует размещать в отдельной кэш-линии, для этого в C++17 существует коснтрукция alignas(std::hardware_destructive_interference_size), а в более ранних можно использовать процессор-зависимое решение char tmp[60]; (на x86_64 размер кэш-линии 64 байта):
Перед установкой флага, читатель проверяет есть ли писатель – т.е. есть ли эксклюзивная блокировка. А т.к. shared-mutex применяется в случаях, когда очень мало писателей, то все используемые ядра могут иметь копию этого значения у себя в кэш-L1 в shared-state (S) откуда за 3 такта получат значение флага писателя, пока он не изменится.
Для всех писателей, как обычно, есть один и тот же флаг want_x_lock – он означает, что в данный момент есть писатель. Потоки писатели его устанавливают и снимают, используя RMW-операции.
Но чтобы читатели не мешали друг другу, для этого они должны писать информацию о своих shared-блокировках в разные ячейки памяти. Выделим массив под эти блокировки, размер которого мы будем задавать параметром шаблона, по умолчанию равным 20. При первом вызове lock_shared() поток автоматически будет регистрироваться – занимая определенное место в этом массиве. Если потоков больше, чем размер массива, то остальные потоки при вызове lock_shared() будут вызывать эксклюзивную блокировку писателя. Потоки создаются редко, а время, затраченное операционной системой на их создание настолько велико, что время на регистрацию нового потока в нашем объекте будет ничтожно мало.
Удостоверимся, что нет очевидных ошибок – на примерах покажем, что все работает верно, а далее схематически докажем правильность нашего подхода.
Приведем пример такой быстрой разделяемой блокировки, в которой читатели не мешают друг другу: [30] coliru.stacked-crooked.com/a/b78467b7a3885e5b
Рассмотрим алгоритм нашего contention_free_shared_mutex для случаев, когда 2 потока пытаются одновременно выполнить операции:
Поток-писатель в нашей схеме имеет выше приоритет, чем читатель. И если они одновременно установили свои флаги блокировок, то поток-читатель следующей операцией проверяет есть ли поток-писатель (want_x_lock == true), и если есть, то читатель отменяет свою блокировку. Поток-писатель видит, что больше нет блокировок от читателей и успешно завершает функцию блокирования. Глобальный порядок этих блокировок соблюдается благодаря семантике Sequential Consistency (std::memory_order_seq_cst).
Схематически взаимодействие двух потоков (читателя и писателя) выглядит следующим образом.
В обеих функциях lock_shared() и lock(), для обеих операций 1. и 2. используется std::memory_order_seq_cst — т.е. для этих операций гарантируется единый порядок для всех потоков (single total order).
Автоматическая отмена регистрации потока в cont-free shared-mutex
Чтобы решить эту проблему – добавим автоматическую отмену регистрации потока, когда поток удаляется. Если поток работал с множеством таких блокировок, то в момент завершения потока должна произойти отмена регистрации во всех таких блокировках. А если в момент удаления потока есть блокировки, которые на данный момент уже удалены, то не должно произойти ошибки из-за попытки отменить регистрацию в несуществующей блокировке.
Как видим сначала зарегистрировались 20 потоков, а после их удаления и создания новых 20 потоков, они также смогли зарегистрироваться – под теми же номерами register_thread = 0 – 19 (смотрите вывод(output) примера).
Теперь покажем, что даже если потоки работали с блокировкой, а затем блокировка была удалена, то при завершении потоков не произойдет ошибки из-за попытки отменить регистрацию в несуществующем объекте блокировки: [35] coliru.stacked-crooked.com/a/d2e5a4ba1cd787da
Мы установили таймеры так, чтобы создались 20 потоков, выполнили чтение используя нашу блокировку и заснули на 500ms, а в это время через 100ms удалился объект contfree_safe_ptr содержащий внутри себя нашу блокировку contention_free_shared_mutex, и только после этого проснулись 20 потоков и завершились. При завершении их не произошло ошибки из-за отмены регистрации в удаленном объекте блокировки.
Как небольшое дополнение сделаем поддержку MSVS2013, чтобы владельцы старого компилятора тоже смогли ознакомиться с примером. Добавим упрощенную поддержку регистрации потоков, но без возможности отмены регистрацию потока.
Конечный результат покажем в виде примера, в котором учтены все перечисленные выше мысли.
Правильное функционирование алгоритма и выбранных барьеров
Выше мы провели тесты, которые показали отсутствие очевидных ошибок. Но чтобы доказать работоспособность необходимо создать схему возможных изменений порядка операций и возможных состояний переменных.
Эксклюзивная блокировка lock() / unlock() почти такая же простая, как и в случае с recursive_spinlock_t, поэтому её детально рассматривать не будем.
Конкуренцию потока-читателя за lock_shared() и потока писателя за lock() мы детально рассмотрели выше.
Сейчас основная задача показать, что lock_shared() во всех случаях использует как минимум Acquire-semantic, а unlock_shared() во всех случаях использует как минимум Release-semantic. Но это не обязательное условие при повторном рекурсивном блокировкии/разблокировании.
Теперь покажем, как эти барьеры реализуются в нашем коде.
Схематически барьеры для lock_shared() – красными перечеркнутыми стрелками показаны направления в которых изменение порядка запрещено:
Схематически барьеры для unlock_shared():
Приведем также блок-схему этой же самой функции lock_shared()
В овальных блоках обозначены строгие последовательности исполнения операций:
Т.к. выполняются 2 условия – то считается, что все необходимые side-effects от многопоточности учтены:
Все остальные функции нашей блокировки «contention_free_shared_mutex» более очевидны с точки зрения логики многопоточного исполнения.
Так же отмечу, что при повторном (рекурсивном) блокировании, атомарным операциям не обязательно иметь барьер std::memory_order_acquire (как показано на рисунке), достаточно установить std::memory_order_relaxed, потому что это не фактический вход в блокировку — мы уже находимся в блокировке. Но это не добавляет большой скорости и может усложнить понимание.
Как использовать
Покажем пример использования contention_free_shared_mutex<> на C++ в качестве высоко-оптимизированного shared_mutex.
Чтобы скомпилировать данный пример на Clang++ 3.8.0 под Linux вы должны изменить Makefile.
Этот код в онлайн компиляторе: coliru.stacked-crooked.com/a/11c191b06aeb5fb6
Как вы видите наш мьютекс sf::contention_free_shared_mutex<> используется точно таким же образом, как и стандартный std::shared_mutex.
Тест: std::shared_mutex VS contention_free_shared_mutex
Приведем пример тестирования на 16 потоках для одного серверного процессора Intel Xeon E5-2660 v3 2.6 GHz. В первую очередь нас интересует голубая и фиолетовая линии:
Командная строка для запуска:
Если у вас только один CPU на материнской плате, то запускайте: ./benchmark
Производительность различных блокировок для разного соотношения блокировок чтения (shared-lock) и блокировок записи (exclusive-lock).
Performance (the bigger – the better), MOps — millions operations per second
Количество потоков 36 для contention-free задается параметром шаблона и может быть изменено.
Теперь покажем медианную задержку для разного соотношения типа блокировок: чтения (shared-lock) и записи (exclusive-lock).
В коде теста main.cpp необходимо установить: const bool measure_latency = true;
Командная строка для запуска:
Если у вас только один CPU на материнской плате, то запускайте: ./benchmark
Median-latency (the lower – the better), microseconds
Таким образом мы создали разделяемую блокировку, в которой читатели не мешают друг другу во время блокирования и разблокирования, в отличие от std::shared_timed_mutex и boost::shared_mutex. Но у нас для каждого потока дополнительно выделяется: 64 байта в массиве блокировок + 24 байта занимает структура unregister_t для отмены регистрации + элемент, указывающий на эту структуру из hash_map. В сумме около 100 байт на поток.
Более глубокая проблема – это возможность масштабироваться. Например, если у вас есть 8 CPU (Intel Xeon Processor E7-8890 v4) по 24 ядра (по 48 потоков HyperThreading), то это в сумме 384 логических ядра. Каждый поток-писатель перед записью должен прочитать по 24576 байт (по 64 байт от каждого из 384 ядер), но читать их можно параллельно, это конечно лучше, чем ждать пока одна кэш-линия последовательно перейдет от каждого из 384 потока к каждому, как в обычных std::shared_timed_mutex и boost::shared_mutex (при любом типе блокировки unique/shared). Но распараллеливание на 1000 ядер и более обычно реализуется через другой подход, а не через вызов атомарной операции на обработку каждого сообщения. Все обсуждаемые выше варианты: атомарные операции, активные блокировки, lock-free структуры данных – это все необходимо для низких задержек (0,5 – 5 usec) отдельных сообщений.
Для высоких показателей количества операций в секунду, т.е. для высокой производительности системы в целом и масштабируемости на десятки тысяч логических ядер используют подходы массового параллелизма, «hide latency» и «batch processing» — пакетной обработки, когда сообщения сортируются (для map) или группируются (для hash_map) и сливаются с уже имеющимся отсортированным или сгруппированным массивом за 50 – 500 usec. В итоге у каждой операции задержка в 10-100 раз больше, но эти задержки происходят в одно и тоже время в огромном количестве потоков, в результате происходит сокрытие задержек «hide latency» за счет использования «Fine-grained Temporal multithreading».
Если предположим: у каждого сообщения задержка в 100 раз больше, но сообщений обрабатывается в 10000 раз больше. Это на единицу времени в 100 раз эффективней. Такие принципы применяются при разработке на GPU. Возможно в следующих статьях мы разберем это подробней.