Непровар это: Непровар сварного шва — в чем заключается основная опасность

Содержание

Непровар сварного шва — в чем заключается основная опасность

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

Что такое непровар

В справочниках непроваром называются дефекты в местах, где основной и наплавленный металл не сплавились или сечение шва заполнено не полностью. Изъяны этого вида образуются на кромках, между слоями при сварке широких стыков в несколько проходов, в корне шва. На изломах и вырезанных для контроля участках непровар выглядит как темная полоска между основным и наплавленным металлом. Визуально изъян можно обнаружить по наружным трещинам и порам, которые образуются на внешней поверхности сварного шва. Внутренние трещины и другие нарушения выявляют методом просвечивания сварного соединения рентгеном, гамма-лучами, ультразвуком.

Виды непровара сварного шва

Причины возникновения

Причиной, по которой образуются непровары, может стать:

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

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

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

Трещины, сопровождающие непровары, образуются на шве и прилегающих к нему участках внутри или снаружи. Дефекты могут располагаться в продольном и поперечном направлении. Трещины опасны тем, что имея микроскопические начальные размеры, под действием остаточного напряжения с высокой скоростью увеличиваются до больших размеров. Риск образования трещин возрастает при сварке жестко закрепленных заготовок, металлов с высоким содержанием серы, фосфора, углерода. Выполнение монтажа на морозе и наложение близко расположенных швов также чревато появлением трещин.

Группы непровара по ГОСТ

В ГОСТ 30242-97 дефекты сварных швов связанные с непроваром представлены в 6 группах:

№ группы

Название дефектов

1

Трещины

2

Поры и полости

3

Твердые включения

4

Не сплавления и непровары

5

Нарушения формы шва

6

Дефекты, не вошедшие в предыдущие группы

Каждый дефект обозначается трехзначным числом, начинающимся с номера группы. Например, 101 — продольная трещина, а 402 непровар. Для указания места расположения изъяна добавляется четвертая цифра. Так кодом 4011 обозначено несплавление между швом и основным металлом по боковой стороне. В справочниках Международного института сварки (МИС) дефекты обозначены буквами латинского алфавита. Непровары и несплавления маркируются литерой D.

Несплавление сварочного шва: 4011 — по боковой стороне; 4012 — между валиками; 4013 — в корне шва

Способы предотвращения непроваров

Основная опасность таких дефектов как непровары заключается в снижении прочности шва до 70%. Поэтому проще и дешевле предотвратить их образование, чем переделывать работу заново.

Разделка кромок

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

Подача тепла

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

Соблюдение режимов сварки

У неопытных сварщиков шов с непроварами получается при установке низкого значения тока, несоответствующего толщине и металлу заготовок. При неправильном выборе полярности, если сварка выполняется постоянным током, оксидные пленки электромагнитным полем прижимаются к кромкам, препятствуя их расплавлению. Также следует учитывать, что магнитное поле может отклонять дугу в сторону. Чтобы избежать появления непроваров на концах шва и при замене электрода у многофункциональных инверторов настраивают функцию кратковременного повышения напряжения. Она срабатывает в начале и в момент отрыва электрода. Высока вероятность образования изъянов при работе на старом аппарате без стабилизации параметров дуги. Ее ток будет изменяться при колебаниях сетевого напряжения. Поэтому для работы выбирают время, когда они минимальны.

Положение электрода при сварке

При сварке дугу ведут строго по оси стыка, чтобы обе кромки прогревались одинаково, так как при отклонении в сторону одна из них не сплавится со швом. Электрод перемещают углом вперед, с наклоном 5 — 20⁰. Сварка угловых швов выполняется «в лодочку», электрод держат на равном расстоянии от поверхности заготовок. Если детали соединяют несимметричной «лодочкой», электрод выставляют под углом 30⁰ к плоскости одной из них. Сварку проводят на повышенном токе с прямой или обратной полярностью. При сварке обратной полярностью, выполняемой короткой дугой, могут образоваться подрезы. Завышенный диаметр электрода способствует попаданию частичек шлака в промежуток между кромками.

Тугоплавкие оксиды

Легированная сталь и сплавы содержат компоненты, которые при нагреве образуют тугоплавкие оксиды. При нарушении технологии сварочных работ они и шлак остаются внутри шва, создавая непровары. Поскольку для образования оксидов необходим кислород, нужно защищать сварочную ванну от контакта с атмосферой. Если сварку выполняют инвертором MIG/MAG, используется инертный газ или флюс, создающий при нагреве защитную среду. При сварке плавящимся электродом важно правильно подобрать марку с покрытием, соответствующему виду металла.

Как исправить дефект непровара

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

Нормативными документами допускается образование непроваров, если они не превышают установленные пределы. Поэтому начинающим сварщикам не стоит бояться дефектов, главное своевременно устранять их. Зная причины образования непроваров, можно быстро научиться выявлять места их появления.

Распространенные дефекты сварных швов и методы их контроля

Время чтения: ≈12 минут

Не важно, какую технологию вы выбрали для выполнения сварочных работ. Дефекты могут возникнуть в любом случае, что при ручной дуговой сварке, что при сварке под флюсом с применением автоматического сварочного аппарата. Появление дефектов связано либо с неопытностью сварщика, либо с неправильно выбранным режимом сварки, либо с недостаточно тщательным контролем качества.

Поэтому важно предотвращать дефекты и контроль качества сварных соединений должен проводится после выполнения каждой сварочной операции. В этой статье мы подробно расскажем, какие существуют распространенные дефекты сварных швов. И какие методы контроля можно использовать, чтобы обнаружить их.

Содержание статьи

Распространенные дефекты

Любой опытный сварщик скажет вам, что существуют многочисленные виды дефектов сварных швов. Их можно разделить на две категории — наружные и внутренние. Наружные дефекты сварных швов можно обнаружить прямо на поверхности шва с помощью специального инструмента (например, лупы) или хорошего зрения. Внутренние дефекты сварных швов визуально не видны и для их обнаружения нужно использовать особые методики контроля качества. О них мы расскажем ближе к концу. А пока дефекты.

В рамках этой статьи мы не будем перечислять все возможные дефекты, а расскажем только о самых распространенных. Итак, ниже наша краткая классификация дефектов сварных швов.

Непровар

Непровар в сварном шве — один из самых часто встречающихся дефектов у новичков. Представляет собой небольшой участок с недостаточно проваренным металлом. Основные причины образования непроваров — слишком длинная сварочная дуга, недостаточная сила тока или обе ошибки одновременно.

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

Подрез

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

Читайте также: Исправление дефектов сварки 

Некоторые новички спрашивают: «Допускаются ли подрезы сварных швов?».  Да, но только в очень сложных конструкциях, где подрезов не избежать. В подобных ситуациях подрезы называют просто «допустимые дефекты сварных швов». В остальных случаях это недопустимые дефекты.

Наплыв

Наплыв в сварном шве в 95% случаев свидетельствует о том, что вы неправильно настроили режим сварки или недостаточно тщательно зачистили кромки. Очевидно, что для предотвращения образования дефекта нужно правильно настроить силу сварочного тока и немного повысить напряжение дуги.

Прожог

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

Понизьте сварочный ток и ускорьте формирование шва. Только так вы сможете предотвратить появление прожогов. Уделите особое внимание, если варите алюминий. У него очень высокая теплопроводность, при этом низкая температура плавления. Так что получить прожог на алюминиевой заготовке проще простого.

Кратер

Кратер — это воронка небольшого размера, расположенная прямо на валике шва. Чаще всего в самом его конце. Образуется из-за резкого обрыва дуги. Ведите дугу плавно и оканчивайте сварку постепенно. Если на вашем сварочном аппарате есть специальный режим предотвращения образования кратеров, то включите его.

Горячая или холодная трещина

Трещины в сварных швах — также один из самых часто встречающихся дефектов. Трещины бывают холодными и горячими. Горячие образуются во время сварки, а холодные — после. Горячие трещины образовываются при несовместимости электрода/присадочной проволоки и свариваемого металла. Иногда трещины могут образоваться при попытке заварить кратер, о котором мы говорили выше. Проверяйте, чтобы состав присадочного материала и металла был идентичен.

Читайте также: Способы предотвращения горячих трещин 

С холодными трещинами все проще. Они образовываются только в том случае, если шов слишком хрупкий и не выдерживает механической нагрузки. Единственный способ предотвратить появление холодных трещин — соблюдать технологию сварки и работать профессионально. Горячие и холодные трещины могут быть как внутренними (скрытыми от глаз), так и наружными.

Поры

Что такое пора в сварке? Пора (а чаще всего поры) — это небольшие углубления в структуре шва. Могут быть поверхностными или внутренними. Представьте муравейник, который пронизывают множественные ходы. Вот то же самое происходит и со швом. Поры без сомнения можно назвать самым частым дефектом из всех возможных.

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

Методы контроля качества

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

Визуально-измерительный контроль

Визуально-измерительный контроль (ВИК) — это самый простой и самый старый способ оценки качества сварного соединения. Из названия понятно, что в ходе этого контроля используется визуальное наблюдение и измерительные приборы. Под визуальным наблюдением подразумевается простой осмотр шва невооруженным глазом или с помощью лупы. В отдельных случаях используют микроскопы. А в качестве измерительных инструментов чаще всего применяют обычные линейки. Это самый доступный и недорогой метод контроля, поскольку инструменты стоят недорого и такому контролю можно обучить самого сварщика, выполняющего работу. Предприятию даже не нужно нанимать отдельных специалистов для проведения этого контроля.

Сейчас в магазинах продаются специальные наборы со всеми необходимыми инструментами и даже подробно инструкцией, как проводить контроль. Вам достаточно один раз прочесть брошюру, все запомнить и вы уже можете провести такой контроль самостоятельно. Но, несмотря на все плюсы, есть у ВИК большой недостаток — значительное влияние человеческого фактора на результат контроля. Вся ответственность ложится на плечи человека. И если он в силу объективных или субъективных причин не сможет выполнить контроль качественно, то есть вероятность брака.

Радиационный контроль

Радиационный контроль (его также называют радиографическим) — очень интересный метод контроля, который основан на применение рентгеновских лучей. Да, как при рентген-диагностике в поликлинике. Деталь повещается в специальный аппарат (или аппарат устанавливается на деталь), затем сквозь металл пропускают рентгеновское излучение и на выходе получают снимок, на котором видны все дефекты сварки. Эта технология наверняка известна вам давно.

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

Ультразвуковой контроль

Ультразвуковая дефектоскопия сварных швов (он же ультразвуковой контроль качества или просто УЗК сварных швов) — метод контроля, который во многом схож с выше описанным радиационным. Только вот вместо рентгеновских лучей здесь используются ультразвуковые волны. Для фиксации результата используется ультразвуковой дефектоскоп для контроля сварных соединений.

Суть его работы проста. На поверхность шва посылаются ультразвуковые волны, которые проходят сквозь металл. Проходят не полностью, часть лучей отражается и возвращается обратно. Если у шва есть какой-либо дефект, то отразившиеся и вернувшиеся назад волны будут ослаблены и искажены. Проще говоря, они будут отличаться от тех, что были пущены вначале проведения контроля. Все эти изменения как раз и фиксирует дефектоскоп.

Читайте также: Неразрушающий контроль сварных соединений 

Ультразвуковой контроль используется очень часто. Для его проведения можно установить большой стационарный дефектоскоп в отдельном кабинете, а можно приобрести компактную модель для выездной диагностики. И эта компактная модель сможет дать вполне объективный результата. С помощью дефектоскопа можно не только узнать местонахождение дефекта, но и его размеры. Но нужно учитывать, что дефектоскопы стоят дорого и для работы с ними нужно дополнительно обучать персонал. Или искать специалиста «на стороне».

Вместо заключения

Дефекты сварных швов и соединений бывают разными, но суть всегда одна — они так или иначе нарушают эксплуатационные характеристики готового изделия. Чтобы их избежать необходимо как можно больше практиковаться, правильно настраивать режим сварки и не забывать о контроле качества. Проведение ультразвукового контроля занимает считанные минуты, а в результате вы получаете объективную картину и можете трезво оценить качество своей работы.

[Всего: 2   Средний:  3/5]

непровар — Викисловарь

Содержание

  • 1 Русский
    • 1.1 Морфологические и синтаксические свойства
    • 1.2 Произношение
    • 1.3 Семантические свойства
      • 1.3.1 Значение
      • 1.3.2 Синонимы
      • 1.3.3 Антонимы
      • 1.3.4 Гиперонимы
      • 1.3.5 Гипонимы
    • 1.4 Родственные слова
    • 1.5 Этимология
    • 1.6 Фразеологизмы и устойчивые сочетания
    • 1.7 Перевод
    • 1.8 Библиография
В Викиданных есть лексема непровар (L133629).

Морфологические и синтаксические свойства[править]

падеж ед. ч.
мн. ч.
Им. непрова́р непрова́ры
Р. непрова́ра непрова́ров
Д. непрова́ру непрова́рам
В. непрова́р непрова́ры
Тв. непрова́ром непрова́рами
Пр. непрова́ре непрова́рах

не-про-ва́р

Существительное, неодушевлённое, мужской род, 2-е склонение (тип склонения 1a по классификации А. А. Зализняка).

Приставки: не-про-; корень: -вар-.

Произношение[править]

  • МФА: [nʲɪprɐˈvar]

Семантические свойства[править]

Значение[править]
  1. спец. недостаточная степень, глубина проваривания шва при сварке ◆ Отсутствует пример употребления (см. рекомендации).
Синонимы[править]
Антонимы[править]
Гиперонимы[править]
Гипонимы[править]

Родственные слова[править]

Ближайшее родство

Этимология[править]

Происходит от ??

Фразеологизмы и устойчивые сочетания[править]

Перевод[править]

Список переводов

Библиография[править]

  • Словарь новых слов русского языка (середина 50-х — середина 80-х годов) / Под ред. Н. З. Котеловой. — СПб. : Дмитрий Буланин, 1995. — ISBN 5-86007-016-0.
Для улучшения этой статьи желательно:
  • Добавить пример словоупотребления для значения с помощью {{пример}}
  • Добавить синонимы в секцию «Семантические свойства»
  • Добавить гиперонимы в секцию «Семантические свойства»
  • Добавить сведения об этимологии в секцию «Этимология»
  • Добавить хотя бы один перевод в секцию «Перевод»

Группа 4. Несплавление и непровар — Студопедия.Нет

Несплавление

Отсутствие соединения между металлом сварного шва и основным металлом или между отдельными валиками сварного шва.

                   Различают несплавления:

                 — по боковой стороне;

       — между валиками;

       — в корне сварного шва

 

Непровар

Несплавление основного металла по всей длине шва или на участке, возникающее вследствие неспособности расплавленного металла проникнуть в корень соединения

 

 

Группа 5. Нарушение формы шва

 

Нарушение формы

Отклонение формы наружных поверхностей сварного шва или геометрии соединения от установленного значения

 

Подрез

Углубление продольное на наружной поверхности валика сварного шва, образовавшееся при сварке

 

Подрез со стороны корня одностороннего сварного шва, вызванная усадкой но границе сплавления

 

 

Превышение выпуклости

Избыток наплавленного металла на лицевой стороне стыкового шва сверх установленного значения

 

 

 

 

Превышение проплава

Избыток наплавленного металла на обратной стороне стыкового шва сверх установленного значения

 

Наплыв

Избыток наплавленного металла сварного шва, натекший на поверхность основного металла, но не сплавленный с ним

 

Линейное смещение

Смещение между двумя свариваемыми элементами, при котором их поверхности располагаются параллельно, но не на требуемом уровне

 

Угловое смещение

Смещение между двумя свариваемыми элементами, при котором их поверхности располагаются под углом, отличающимся от требуемого

 

Натек

Металл сварного шва, осевший вследствие действия силы тяжести и не имеющий сплавления с соединяемой поверхностью.

В зависимости от условий это может быть:

натек при горизонтальном положении
сварки;

натек в нижнем или потолочном положении сварки;

натек в угловом сварном шве;

Натекание в шве нахлесточного соединения

 

Прожог

Вытекание металла сварочной ванны, в результате которого образуется сквозное отверстие в сварном шве

 

Не полностью заполненная разделка кромок

Продольная непрерывная или прерывистая канавка на поверхности сварного шва из-за недостаточности присадочного металла при сварке

 

Чрезмерная асимметрия шва

Чрезмерное превышение размеров одного катета над другим

 

 

Неравномерная ширина шва

Отклонение ширины от установленного значения вдоль сварного шва

 

 

Вогнутость корня шва

Неглубокая канавка со стороны корня одностороннего сварного шва, образовавшаяся вследствие усадки

 

Возобновление шва

Местная неровность поверхности и в месте возобновления сварки

 

Группа 6. Прочие дефекты

 

Нарушение формы

Отклонение формы наружных поверхностей сварного шва или геометрии соединения от установленного значения

 

Случайная дуга

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

 

 

Брызги металла

Капли наплавленного или присадочного металла, образовавшиеся во время сварки и прилипшие к поверхности затвердевшего металла сварного шва или околошовной зоны основного металла

 

 

Вольфрамовые брызги

Частицы вольфрама, выброшенные из расплавленной зоны электрода на поверхность основного металла или затвердевшего металла сварного шва

Поверхностные задиры

Повреждение поверхности, вызванное удалением временно приваренного приспособления

 

 

Утонение металла

Уменьшение толщины металла до значения менее допустимого при механической обработке

 

Непровар в полуавтомате

В процессе работы оборудования может возникнуть такая неисправность как непровар металла. Чтобы рассмотреть данную проблему детально, для начала разделим все аппараты MIG/MAG на полуавтоматы инверторного и трансформаторного типа.

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

В трехфазных аппаратах, при перемещении по сварочному посту, в сетевой вилке может вылететь одна фаза или повредиться кабель питания. Также следует обратить внимание на клемму заземления и состояние сварочной горелки. Поскольку аппарат работает в жесткой характеристике, все сварочные соединения должны быть надежными и не иметь следов разрушений.

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

Что касается трансформаторных источников, то в них следует обратить внимание на выходной диодный мост, на котором визуально можно проверить целостность диодов. Далее следует обратить внимание на контактор. Он работает в интенсивном режиме, включается и выключается на больших токах. Поскольку это механическая часть, она подвержена быстрому износу.

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

Таким образом, если Вы не выявили при визуальном осмотре ничего из вышеперечисленного, советуем обратиться в квалифицированный сервисный центр.

 

 

Неплавление сварных соединений

Неплавление сварных соединений

Конспект.

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

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

    Испытания проводились на типовых сварных соединениях. Металлографические и механические испытания также использовались для сравнения с неразрушающим контролем. Различные типы отсутствия слияния были разделены на характерные группы, т.е.е. типы, включая пустоты и неметаллические включения, и типы чистого отсутствия плавления, которые не могут быть обнаружены неразрушающим контролем.
    Ключевые слова: сварка, неразрушающий контроль, дефекты шва, неплавление.

1 Введение

    Из практики сварки известно, что дефекты неплавления часто возникают в сварном соединении. Таким образом, многие разрушения сварной конструкции происходят из-за необнаруженного отсутствия плавления в сварных соединениях.

    Отсутствие плавления — один из самых серьезных дефектов сварного шва.Создает эффект выемки. Однако эффективных неразрушающих методов его обнаружения не существует.

    Если сравнить дефекты неплавления и трещины, становится очевидным, что гораздо больше внимания уделяется трещинам, чем отсутствию плавления, хотя отсутствие плавления является таким же серьезным дефектом, как и трещина. В литературе по сварке множество работ посвящено трещинам, но очень мало работ по неплавлению. Было обнаружено, что только некоторые старые документы Международного института сварки сообщают об отсутствии плавления [1-4].Поэтому было решено более внимательно изучить отсутствие термоядерного синтеза. Требовалось выяснить, где он встречается чаще всего, при каких условиях образуется и как его можно обнаружить.

2 Определение отсутствия проплавления

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

    Что касается положения дефектов неплавления в сварном шве, различают три типа неплавления [5]:

  1. отсутствие проплавления боковых стенок,
  2. отсутствие межпотоковой сварки,
  3. Отсутствие проплавления в основании сварного шва.

Что касается внешнего вида поверхности трещины, различают отсутствие плавления из-за нерасплавленных оксидных включений и отсутствие плавления из-за расплавленных оксидных включений.Неплавкие дефекты из-за нерасплавленных оксидных включений состоят из оксидов и неметаллических включений. Отсутствие сплавления, из которых три типа, то есть ссылки 4011, 4012 и 4013, различаются в стандарте, не следует путать с отсутствием проникновения, то есть ссылочным номером 402 IIW [6]. Дефекты, расположенные на поверхности, эффективно обнаруживаются при визуальном осмотре. Однако отсутствие провара внутри сварного шва можно обнаружить с помощью методов рентгеновского или ультразвукового контроля.

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

3 Характеристики непровара

    При металлографических исследованиях было обнаружено, что в сварном шве могут быть обнаружены три типа неплавления:
  1. чистое отсутствие плавления или отсутствие плавления из-за включений расплавленных оксидов,
  2. открытая расплавка,
  3. Неплавление, состоящее из неметаллических включений.

Полное отсутствие плавления является дефектом конструкции. В этом случае расплавленный металл прилипает к основному металлу, который недостаточно расплавился во время сварки. Между твердой и жидкой фазами образуется стык. Это похоже на пайку. Этот тип отсутствия плавления не может быть обнаружен методами неразрушающего контроля, а только с помощью микроскопического исследования. Прямая линия плавления указывает на то, что может быть отсутствие плавления между основным металлом и сварным швом. Отсутствие плавления между прогонами еще более скрыто.Его можно обнаружить только при точном микроскопическом исследовании с 50-кратным увеличением. Пример чистого отсутствия термоядерного синтеза показан на рис.1.

Рис. 1: Чистое отсутствие сплавления между конечным сплавом и основным металлом. а) макросъемка: x3,5; микрофотография: x100.

Из-за внутренних напряжений, возникающих во время затвердевания и охлаждения сварного шва, прилипающие друг к другу грани разделятся.Образуется пустота шириной всего несколько сотых миллиметра. Этот зазор в сварном шве очень грязный, как трещина. Однако его можно обнаружить методами неразрушающего контроля. Такой вид неплавления сложно отличить от трещины. Пример открытого неплавления показан на рис.2.

Рис. 2: Открытое отсутствие слияния между центральным и конечным проходами. а) макрограф, x3.5; б) микрофотография: x100.

Там, где нет плавления, очень часто встречаются оксиды и неметаллические включения. Такой случай показан на рис. 3. Если оксидный слой не плавится, включения равномерно распределяются по всей поверхности неплавкого дефекта. Однако если они плавятся, неметаллические включения приобретают сферическую форму.

Рис. 3: Включения на гранях слипаются.а) Макрофотография показывает отсутствие слияния между центральным и конечным проходами; б) На микрофотографии видны включения на гранях, склеенные вместе.

4 Расположение дефектов неплавления

    Отсутствие сплавления — это плоский дефект. Он может появиться на краю основного металла или между прогонами. Отсутствие сплавления между основным металлом и металлом сварного шва показывает плоскую поверхность. Однако отсутствие межпроходного плавления показывает неправильную форму.

    Отсутствие плавления обычно обнаруживается на внутренней стороне сварного шва. Он редко достигает финальных прогонов или корневого прогона. Расположение типичных типов неплавления показано на рис.4.

    Рис. 4: Отсутствие проплавления сварного шва: отсутствие проплавления боковой стенки (вверху), отсутствие проплавления между проходами (внизу).

5 Обнаружение отсутствия плавления методами неразрушающего контроля

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

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

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

6 Методы неразрушающего контроля для обнаружения отсутствия плавления

    Рентгенографическое исследование позволяет обнаружить большие, отличительные типы несращения, в которых есть включения и пустоты значительного размера.Обследование обнаруживает включения, но не само отсутствие плавления. Следует обращать внимание на все дефекты, обнаруженные на линии сварки и между отдельными циклами. Рентгенографические методы исследования не подходят для обнаружения отсутствия плавления.

    Для обнаружения отсутствия плавления ультразвуковое исследование является наиболее подходящим из всех доступных методов неразрушающего контроля. Могут быть обнаружены все типы отсутствия плавления, за исключением полного отсутствия плавления.Наши исследования показали, что значительно большие дефекты неплавления дают слабые ультразвуковые сигналы, которые на практике считаются незначительными допустимыми дефектами. Значительно плохое отражение ультразвуковых волн достигается из-за:

    • склеенная поверхность состоит из ряда мелких дефектов, которые переходят в чистое отсутствие плавления;
    • Неплавление на краях V-образного шва совпадает с углом отражения ультразвуковых волн.
    Рис. 5: Ультразвуковое исследование отсутствия плавления основного металла и металла шва.а) осмотр со всех четырех направлений сканирования; б) осмотр с покровного слоя.

    Из-за вышеизложенного следует также учитывать слабые повторяющиеся признаки при осмотре сварных швов, в которых предполагается отсутствие плавления. Ультразвуковые волны следует направлять по возможности перпендикулярно к прилипающим поверхностям. Можно предположить, что неплавление может появиться на краю основного металла. В случае, когда доступ ультразвуковых волн возможен со всех четырех сторон, прямой путь, как показано на рис.5а. Осмотр можно проводить только с покровного слоя. В этом случае метод одиночного отскока используется для исследования верхней части сварного шва (рис. 5b).

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

    Все обозначения на краю рядов указывают на возможность отсутствия проплавления. Особое внимание следует уделить угловым швам.Дефект неплавления, существующий в сварном шве, может открыться под нагрузкой и вызвать утечку из бака.

    Отсутствие сварки — это обычно ошибка сварщика. Наблюдая за процессом сварки, опытный супервайзер может обнаружить образование неплавления. Следует обратить внимание на положение сварочной ванны, которое не должно опережать дугу и заполнять сварную канавку. Следует обратить внимание на то, достаточно ли сварщик расплавляет края стыка. Отсутствие плавления можно успешно обнаружить при визуальном осмотре во время сварки.Визуальный осмотр после сварки неэффективен.

7 Выводы

    В сварных швах могут наблюдаться как отсутствие плавления, которое является дефектом конструкции, так и отсутствие плавления, при котором металлическое соединение прерывается пустотами и неметаллическими включениями. Чистое отсутствие плавления можно определить только визуально во время сварки. Другие типы отсутствия плавления могут быть обнаружены методами ультразвукового контроля. Отсутствие плавления, достигающего поверхности, может быть обнаружено методами испытания проникающей жидкостью или магнитными частицами.Из-за неблагоприятного положения из-за отсутствия плавления и неблагоприятных свойств прилипающих поверхностей слабые показания получаются при значительно больших дефектах; поэтому критерии приемки для сварных швов, содержащих неплавкие соединения, должны быть более строгими.

Ссылки

  1. Н. Ямаути, Ю. Инаба, Т. Така: Механизм образования недостаточного плавления при сварке MAG. IIW Док. 212-529-82. Международный институт сварки, 1982 г.
  2. Причины дефектов сварных швов.IIW Док. XII-В-046-83. Международный институт сварки, 1983 г.
  3. Металло-дуговая сварка стали в среде защитных газов. Направления выполнения процесса. Избежание отсутствия плавления. IIW Док. XII-В-049-83. Международный институт сварки, 1983 г.
  4. Р. Киллинг и Х. Хантч: Beitrag zur Frage der Bindefehlerempfindlichkeit beim Metall-Aktivgasschweißen mit Fülldrahtelektroden. Schweißen und Schneiden, 45 (1993) 12, 689-693.
  5. Многоязычный сборник терминов для сварки и родственных процессов / Международный институт сварки.Часть 1. Общие условия. Институт за варильство, Любляна, 1988.
  6. EN 26520: Классификация дефектов металлических сварных швов плавлением с пояснениями.
Справочник

— Дефекты сварных швов

Справочник — Дефекты сварных швов

3 Продолжение на следующей странице… ОТСУТСТВИЕ СЛИЯНИЯ Отсутствие плавления, также называемое холодной притиркой или холодный шов, когда нет плавления металла шва и поверхности опорной плиты. Этот дефект можно увидеть на рис. 10-2. Самая частая причина нехватки сварки — это плохая техника сварки. Либо сварочная ванна слишком велика (слишком низкая скорость движения), либо металл сварного шва был допущен к прокатке перед дугой. Опять же, дугу нужно держать на передней кромке лужа.Когда это будет сделано, сварочная ванна не станет слишком большой и не сможет погасить дугу. Другая причина — использование очень широкий сварной шов. Если дуга направлена ​​вниз по центру стыка, расплавленный металл шва будет только течь и литой против боковых стенок опорной плиты без их плавления. В тепло дуги необходимо использовать для плавления опорная плита. Это достигается за счет сужения стыка или за счет направляя дугу в сторону стенка опорной плиты.При многопроходной сварке толстого материала расслоение валика по возможности следует использовать технику после прохождения рута. Большие сварные швы перекрывают весь следует избегать разрыва. Отсутствие плавления может также произойти в форма закрученной бусиной короны. Опять же, это обычно вызвано очень низкая скорость движения и попытки сделайте слишком большой сварной шов за один проход. Однако также очень часто вызвано слишком низким сварочным напряжением. В результате смачивание валика будет плохим.когда сварка алюминия, частая причина дефекта такого типа — наличие алюминия окись. Этот оксид это огнеупор с температурой плавления приблизительно 3500 0 F (1927 0 C). Он также не растворяется в расплаве. алюминий. Если этот оксид присутствует на свариваемых поверхностях сплавление с металлом шва будет затруднено.

Мы не можем найти эту страницу

(* {{l10n_strings.REQUIRED_FIELD}})

{{l10n_strings.CREATE_NEW_COLLECTION}} *

{{l10n_strings.ADD_COLLECTION_DESCRIPTION}}

{{l10n_strings.COLLECTION_DESCRIPTION}} {{addToCollection.description.length}} / 500 {{l10n_strings.TAGS}} {{$ item}} {{l10n_strings.ТОВАРЫ}} {{l10n_strings.DRAG_TEXT}}

{{l10n_strings.DRAG_TEXT_HELP}}

{{l10n_strings.LANGUAGE}} {{$ select.selected.display}}

{{article.content_lang.display}}

{{l10n_strings.AUTHOR}}

{{l10n_strings.AUTHOR_TOOLTIP_TEXT}}

{{$ select.selected.display}}

{{l10n_strings.CREATE_AND_ADD_TO_COLLECTION_MODAL_BUTTON}}

Архивы LACK OF FUSION — материалы исследования морской инженерии

ПЕРЕЛЕТ

Это перетекание металла шва на основной металл без оплавления. Разрушение соединения определено, когда перекрытие находится на носке сварного шва.

Это один из серьезных дефектов сварки на судах, которого следует избегать. Это может быть вызвано:

  1. Низкий сварочный ток
  2. Быстрое перемещение
  3. Неправильное обращение с электродом

ПОД ОТРЕЗОМ

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

Вероятность разрушения в этой точке увеличивается, если возникает недорез на носке сварного шва; точка есть высокая концентрация напряжения. Этот дефект вызван:

  1. Неправильная манипуляция с дугой
  2. Медленное перемещение
  3. Избыточный сварочный ток

ПОРИСТОСТЬ

Это наличие газовых карманов в сварных швах. Избыточная пористость в металлической дуговой сварке серьезно влияет на механические свойства соединения.

Избегать пористости лучше всего предотвращает:

  1. Перегрев и недогрев металла шва,
  2. Слишком большой ток
  3. Слишком длинная дуга.

ВКЛЮЧЕНИЕ ШЛАКА

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

ОТСУТСТВИЕ ПРОИЗВОДСТВА

Это неспособность наполнителя в основном металле сплавляться вместе в основании соединения. Отсутствие проплавления приведет к разрушению сварного шва, если сварной шов будет подвергаться напряжениям растяжения или изгиба.

Это может быть связано с:

  1. Неправильная конструкция шарнира,
  2. Быстрое перемещение
  3. Электроды слишком большие
  4. Слишком низкая уставка тока.

ОТСУТСТВИЕ СПЛАВЛЕНИЯ

Отсутствие плавления — это неспособность процесса сварки соединить вместе слои металла шва или металла шва и основного металла .

Обычно это называют перекрытием. Отсутствие плавления вызвано невозможностью повысить температуру основного металла или ранее наплавленного металла шва до точки плавления.

Причина сбоя:

  1. Слишком маленький электрод
  2. Слишком быстрое перемещение
  3. Слишком короткий и дуговый зазор
  4. Слишком низкий сварочный ток.

ИСКАЖЕНИЕ

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

— Испанский перевод — Linguee

Pi 500 с строжкой

[…] функция строжки корневых проходов или локуса ti n g отсутствие сплавления .

migatronic.com

Pi 500 включает в себя функцию для

[…] ranurar c или dones de raz o l ocali zar la falta de fusin .

migatronic.com

Независимо от используемой техники ключ

[…] Целью

является создание

[…] есть ли несовершенство (Suc h a s отсутствие сплавления ) o r деградация (например, коррозия) […]

произошло.

applusrtd.com

Independientemente de la tcnica utilizada, el Objetivo

[…]

главный es establecer la

[…] existe NC ia de im pe rfecciones (ej. un d ef ecto en la fusin) o degin .]

(como la corrosin).

applusrtd.com

Fusion p o и заводы r, безусловно, также должны быть улучшены, оптимизированы и затем адаптированы к требованиям en t s из t ч e время.

eur-lex.europa.eu

L as cen tra les de fusin ten dr nt ambi n que pasar por un процесо де mejora, optimizacin y adaptor324 futurois itos tcnicos .

eur-lex.europa.eu

Ее уникальный музыкальный мир открывается для многих влияний, но никогда не падает

[…] в ex ce s s из e c lect i c fusion .

groovalizacion.com

Un universo music abierto e indito que no cae en ningn

[…] momento en el e xc eso del pat ch work heterogneo.

groovalizacion.com

T h e отсутствие c i vi l регистрация […]

продолжает препятствовать их доступу к основным социальным и экономическим правам.

eur-lex.europa.eu

L и и nscr ipci n en el […]

registro civil sigue constraintsando su acceptso a los derechos sociales y econmicos bsicos.

eur-lex.europa.eu

В большинстве случаев недоедание вызвано a отсутствием a c ce ss к пище.

fundacioagbar.org

En la ma yo ra de los c asos, la causa radica en la Dificultad de acc es o a la comida.

fundacioagbar.org

Процесс локального соединения двух и более металлических деталей

[…]

компонентов с помощью

[…] нагрев их поверхностей до a s ta t e плавления , o r путем сплавления с использованием дополнительных […]

присадочный материал.

Hosemaster.com

Процедура локализации компонентов

[…]

metlicos calentando sus superficies a

[…] un es ta do de fusin, o po r fusin c on e l uso de materia le s de r el leno adicionales.

Hosemaster.com

Развитие me n t fusion e n er gy должно активно осуществляться […]

, так что его явные преимущества с точки зрения безопасности и ресурсов

[…]

можно запрячь во второй половине века.

eur-lex.europa.eu

Conviene tambin impulsar enrgicamente

[…] el de sa rroll o de l a ener ga de fusin, pa ra poder b eneficiarse […]

en la segunda mitad de este

[…]

siglo de sus ventajas especficas en materia de seguridad y recursos.

eur-lex.europa.eu

Дополнительная добавленная стоимость —

[…] создано через t h e слияние t h e данные камеры […]

систем и других датчиков транспортных средств.

hella-press.de

Se obtiene una plusvala adicional

[…] fusionando los d at os de lo s sistemas con […]

otros sensores del vehculo.

hella-press.de

Он обосновал это

[…] ссылка на все , например, e d отсутствие o p po rtunity для реализации […]

в соответствующих странах.

европарл.europa.eu

Lo ha justificado

[…] alegand o una supu es ta falta de o port unid ad es para […]

la ejecucin en los pases afectados.

europarl.europa.eu

На основании повсеместной вырубки лесов

[…] Пуэрто-Рико и t h e отсутствие l a ws для защиты […]

Окружающая среда, в своем загородном доме

[…]

Pulguillas открывает центр по сохранению исчезающих тропических растений.

hectormendezcaratini.com

Motivado por la rampante deforestaci n de

[…] Pu erto Ri co y la falta de leye s para […]

el ambiente, en su casa de campo

[…]

ru Pulguillas, inicia un centro de conservacin de plantas tropicales en peligro de extincin.

hectormendezcaratini.com

Итак, когда я вижу отчеты

[…] которые основаны на t h e слияние p r ac tice для слияния, […]

Я должен волноваться.

europarl.europa.eu

As, cuando veo informes que se

[…] basan e n la fusin por la fusin de p r cticas , he de mo st rarme […]

preocupado.

europarl.europa.eu

Проект AQUA очень большой и

[…] важный проект, который слияние t h re e предыдущие системы.

iwt2.org

Проект AQUA es un proyecto bastante extenso e

[…] importante, el cual es un a fusin de t re s s istem as anteriores.

iwt2.org

Будет обнаружено нечто такое, о чем человек не знал, что искал, но что было

[…] обнаружен как побочный продукт du c t of fusion e n er gy собственно исследование.

europarl.europa.eu

Se trata de descubrimientos que no se sabe que se estn buscando, pero que se produn a

[…] Resul ta s de l a vestigaci n de la ener ga d e fusin .

europarl.europa.eu

Chvez появляется convi nc e d из s u ch a ea n s of n a ti onal development.

crisgroup.org

Chvez parece

[…] estar con ve ncido de la ne c esida dd ee sa fusin co mo un des arlo medio media .]

nacional.

crisgroup.org

Я хочу выразить свою четкую поддержку

[…] для развлечения di n g fusion r e se arch.

europarl.europa.eu

Yo quiero manifestar mi claro apoyo a

[…] la research ac в s obr e l a fusin .

europarl.europa.eu

Целью этого будет демонстрация научных и

[…] технологический feasibi li t y fusion e n er gy production.

eur-lex.europa.eu

El Objetivo de sta es demostrar la viabilidad cientfica y tecnolgica

[…] de la produccin d e en erg a de fusin .

eur-lex.europa.eu

Результатом является слияние fe c t d e si gn и технологии.

стопбаг. На

Результат s una p erf ect и fusin de di sen или ec nologia.

stobag.es

Это t h e сплав s t ag e производительность […]

и физические подвиги, позволяющие вызывать эмоции.

cirquedusoleil.com

E st a fusin d e p res encia e n el escenario […]

ylas proezas fsicas es lo que nos permite evocar emociones.

cirquedusoleil.com

Газ нагревается до более чем 100 миллионов градусов Цельсия и будет

[…] производят 500 мегаватт при t s плавления p o we r.

america.gov

El gas se calienta a ms de 100 millones de grados Celsius para producir 500

[…] megav на ios de pot enc ia de fusin .

america.gov

Surg ic a l слияние b o ne s, обычно в […]

позвоночник (артродез)

azkidsheart.com

Fusin qu ir rgic a de l os hueso s, normalmente […]

en la columna vertebral (артродез)

azkidsheart.com

Тепло, выделенное за

[…] кристаллизация была сопоставима с he a t плавлением , i nd icating высокоаморфным […]

характер этой смеси.

netzsch-thermal-analysis.com

El calor liberado durante la cristalizacin es

[…] сопоставимый a l ca lor de fusin, lo qu e ju st ifica el carcter amo rf o de mez

netzsch-thermal-analysis.com

T h i s слияние t e ch niques позволяет […]

хирург для достижения идеальной формы и положения.

docshop.com

E s ta объединение tcn icas p ermite […]

al cirujano alcanzar la forma y la ubicacin ideales.

docshop.com

Прорастание крав-мага было естественным su l t из t h e сплав t рассказов, история Ими и история государства Израиль.

kravmaga.com.br

La aparici n de K rav Maga fue el resultado Natural de la Coalicin de dos Historias, la Historia de Imi y la Historia del Estado de Israel.

kravmaga.com.br

Результат — это a слияние e x pe rtise, энергия и возможности в промышленном мире, в результате […]

различных настроек,

[…]

рынков, климатов и культур.

proteccion-laboral.com

El res ul tado es un a fusin d e expe rien ci as, solera yapidad industrial sur gi das das климат, […]

culturas y mercados.

proteccion-laboral.com

После завершения обучения в SDPRC она начала преподавать

[…] бесплатный класс Zumba s ( a сплав s a ls a и аэробика).

4children.org

Despus de completetar un taller ofrecido por el SDPRC, Lpez empez a ensear una

[…] clase gra tu ita de Zum ba ( un a fusin d e sals a y ae rbic).

es.4children.org

Другой

[…] изображения могут быть отлиты с помощью pr op e r fusion of m e ta ls.

agniyoga.org

Co n una apro pi ada fusin de m et ale s se po dan fundir […]

distintas imgenes.

agniyoga.org

Черная магия и стиль денди сочетаются с прекрасным викторианским

[…] влияет на n e w слияние f e mi девять и мужское начало.

stahl.com

Un negro mgico y un estilo dandi se combinan con maravillosas influencias

[…] victorianas e n una nu ev a fusin d e lo femen in o con […]

lo masculino.

stahl.com

Исторический внутренний город Парамарибо, бывший голландский колониальный город

[…] иллюстрирует гра du a l слияние D u tc h архитектура […]

с местными традициями, внесен в список в 2002 году.

unesco.org

El Centro Histric o de P aramaribo, una ex colonia

[…] holandesa q ue ilu stra l a fusin g ra dual de l a arquitectura […]

Holandesa Con la

[…]

tradicin local, fue inscrito en el 2002.

unesco.org

Оптимизация и объединение GHC — Учебники

Оптимизация и слияние GHC

Первая публикация: 29 ноября 2016 г.
Марк Карпов
тегов: haskell, оптимизация

Протестировано с:


В руководстве подробно рассказывается об использовании прагм GHC, таких как INLINE , SPECIALIZE и RULES , для повышения производительности ваших программ на Haskell.Обычно вы получаете эти знания из руководства пользователя GHC, и это определенно рекомендуется к прочтению, но мы также заметили, что некоторые фрагменты важной информации разбросаны по разным сайтам, таким как Haskell Wiki, не говоря уже о статьях. Это руководство представляет собой попытку показать все важные детали «ноу-хау» оптимизации в одном месте с практическими примерами, протестированными для демонстрации их эффектов.

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

  1. Выберите лучший алгоритм / структуры данных.
  2. Используйте прагмы GHC (описанные в руководстве).
  3. Перепишите критические биты в C.

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

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

Прагмы GHC

Pragmas — это своего рода специальные подсказки для компилятора. Вы должны быть знакомы с прагмой LANGUAGE , которая включает языковые расширения в GHC, например.г .:

  {- # LANGUAGE OverloadedStrings # -}  

Для всех прагм GHC используется один и тот же синтаксис. Технически, все между {- и -} является комментарием, но добавление хешей заставляет GHC следить за прагмами, которые он знает внутри комментария.

Обсудим 3 темы:

  1. Встраивание с прагмами INLINE и INLINABLE .
  2. Специализируется на СПЕЦИАЛЬНО .
  3. Крафт переписывает правила с ПРАВИЛА .

Встраивание

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

Здесь в игру вступает встраивание . Идея проста: просто возьмите тело функции и вставьте его в то место, где оно могло бы быть вызвано. Поскольку встроенные функции обычно короткие, дублирование кода минимально, и мы получаем значительный прирост производительности. Встраивание, пожалуй, самый простой (ну, для конечного пользователя, а не для разработчиков компилятора!) И все же очень эффективный способ повышения производительности.Более того, вскоре мы увидим, что встраивание в GHC — это не только устранение самих вызовов, но и способ применения других оптимизаций.

Как GHC выполняет встраивание самостоятельно

Когда GHC решает, встроить конкретную функцию или нет, он смотрит на ее размер и присваивает какой-то вес этой функции в данном контексте. Правильно, решение о том, вставлять ли функцию или нет, принимается для каждого вызова, и данная функция может быть встроена в одном месте и вызвана в другом месте.Мы не будем вдаваться в подробности того, как вычисляется «вес» (или «стоимость») функции, но должно быть понятно, что чем легче функция, тем более старательным компилятор ее встраивает.

Стоит отметить, что GHC старается избегать чрезмерного раздувания кода и не встраивает вслепую. Обычно функция встраивается только в том случае, если есть хоть какой-то смысл встраивать ее. Принимая решение о встраивании, GHC учитывает следующее:

  • Имеет ли смысл встраивать что-то на определенном сайте вызова? В руководстве пользователя GHC показан следующий пример:

    Здесь, встраивание f приведет к map (\ x -> body) xs , что не лучше оригинала, поэтому GHC не встраивает его.

    Случай, показанный в примере, можно обобщить до следующего правила: GHC встраивает только функции, которые применяются к такому количеству аргументов, сколько они имеют синтаксически в левой части (LHS) определения функции. Это имеет смысл, потому что в противном случае в любом случае потребуется лямбда-перенос.

    Для ясности возьмем еще один пример из руководства пользователя GHC:

      comp1 :: (b -> c) -> (a -> b) -> a -> c
    comp1 f g = \ x -> f (g x)
    
    comp2 :: (b -> c) -> (a -> b) -> a -> c
    comp2 f g x = f (g x)  

    comp1 имеет только два аргумента на своей LHS, а comp2 имеет три аргумента, поэтому такой вызов

    … оптимизирует лучше, чем аналогичный вызов с comp2 .

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

  • Сколько дублирования работы вызовет встраивание? Рассмотрим следующие два примера из статьи «Секреты инлайнера компилятора Glasgow Haskell» (Саймон Пейтон Джонс, Саймон Марлоу):

      пусть x = foo 1000 в x + x  

    … где foo дорого обходится.Встраивание x приведет к двум вызовам foo вместо одного.

    Давайте посмотрим на другой пример:

      пусть x = foo 1000
        е = \ у -> х * у
    в… (ж 3)… (ж 4)  

    Этот пример показывает, что работу можно дублировать, даже если x появляется только один раз. Если мы встроим x в его место возникновения, оно будет оцениваться каждый раз, когда вызывается f . В самом деле, встраивание внутри лямбды может быть опасным делом.

Учитывая приведенные выше случаи, неудивительно, что GHC довольно консервативно относится к дублированию работы.Однако имеет смысл мириться с некоторым дублированием работы, потому что встраивание часто открывает новые возможности преобразования на сайте встраивания. Чтобы было понятнее, избегание самого вызова — не единственная (и фактически не основная) причина для встраивания . Встраивание объединяет фрагменты кода, которые ранее были разделены, что позволяет оптимизатору на следующих этапах выполнять более замечательную работу.

Имея это в виду, вы не должны слишком удивляться, обнаружив, что тело встроенной функции (или правая часть, RHS) не оптимизировано GHC .Это важный момент, к которому мы вернемся позже. Он не оптимизирован, чтобы позволить другим машинам выполнять свою работу после встраивания . Для этого механизма важно, чтобы тело функции было неповрежденным, потому что оно работает на довольно синтаксическом уровне, и оптимизации, если они будут применены, почти не оставят механизму шансов сделать свое дело. А пока помните, что тела функций, которые GHC считает встроенными, не будут оптимизированы, они будут вставлены «как есть». (Тело встроенной функции не будет оптимизировано, и встраивание может также не произойти, поэтому вы можете получить вызов неоптимизированной функции.Не бойтесь, мы узнаем, как исправить это позже в руководстве.)

Один из простейших методов оптимизации, которые GHC может использовать с встраиванием, — это обычное бета-сокращение. Но бета-сокращение в сочетании с встраиванием — это не что иное, как оценка программы во время компиляции. Это означает, что GHC должен каким-то образом гарантировать его завершение.

Это приводит нас к двум крайним случаям:

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

  • При взаимно рекурсивных определениях , GHC выбирает один или несколько прерывателей контура . Прерыватели цикла — это просто функции, которые GHC выбирает для вызова, а не встроенные, чтобы разорвать цикл, в который он попадет, если бы начал встроить все. Например, если у нас есть a , определенные через b и b , определенные через a , мы можем выбрать любой из них в качестве прерывателя шлейфа. GHC старается не выбирать функцию, которую было бы очень полезно встроить (но если у нее нет выбора, она будет).

Наконец, прежде чем мы перейдем к обсуждению того, как можно вручную управлять встраиванием, важно понять пару вещей о том, как хранятся скомпилированные программы Haskell и что GHC может делать с уже скомпилированным кодом Haskell, а чего не может.

Как и многие другие языки, которые компилируются в машинный код, после компиляции, скажем, библиотеки, мы получаем * .o файлов, называемых объектными файлами. Они содержат объектный код, который представляет собой машинный код, который может использоваться в исполняемом файле, но обычно не может быть выполнен сам по себе.Другими словами, это набор скомпилированных исполняемых битов этой библиотеки. Каждый модуль создает собственный объектный файл. Но сложно работать только с объектными файлами, потому что они содержат информацию в не очень удобной форме: вы можете выполнить ее, но вы не можете в целом рассуждать об этом.

Чтобы сохранить дополнительную информацию о скомпилированном модуле, GHC также создает «интерфейсные файлы», которые содержат информацию, например, какой GHC был использован для его компиляции, список модулей, от которых зависит скомпилированный модуль, список вещей, которые он экспортирует и импортирует, и т. Д. прочее; Самое главное, что интерфейсные файлы содержат тела встроенных функций (фактических «разворачиваний») из скомпилированного модуля, поэтому GHC может использовать их для «кросс-модульного встраивания».Это важно понимать: мы не можем встроить функцию, если у нас нет ее тела дословно (помните, GHC встраивает функции без какой-либо обработки, как есть!), И если тело функции не сбрасывается в интерфейс файла, у нас есть только объектный код, который нельзя использовать для встраивания.

Обладая этими знаниями, мы теперь готовы научиться управлять встраиванием вручную.

Как управлять встраиванием

GHC обеспечивает определенный уровень гибкости в отношении встраивания, поэтому есть несколько способов сообщить компилятору, что некоторая функция должна быть встроена (и даже , где она должна быть встроена в ).Поскольку мы только что говорили об интерфейсных файлах, имеет смысл сначала представить прагму INLINEABLE .

Использование прагмы выглядит так:

  myFunction :: Int -> Int
myFunction =…
{- # INLINEABLE myFunction # -}  

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

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

С функцией, помеченной как INLINEABLE , мы можем использовать специальную встроенную функцию с именем inline , которая скажет GHC очень сильно попытаться встроить свой аргумент в конкретный сайт вызова, например:

  foo = bar (встроенная функция myFunction) baz  

Семантически inline — это просто функция идентификации.

Давайте посмотрим на действительный пример INLINEABLE в действии. У нас есть модуль Goaf (что означает «GHC optimizations and fusion», BTW) с этим:

  модуль Goaf
  (inlining0)
где

inlining0 :: Int -> Int
inlining0 x =
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000]  

Здесь я очень старался и убедил GHC, что встраивание сейчас не выглядит очень встраиваемым (ну да, довольно глупый пример, но он демонстрирует суть), если мы скомпилируем с -O2 (как мы будем делать в каждом пример отныне) и дамп Goaf.привет , мы не увидим разворачивания тела inlining0 (если вы используете другую версию GHC, возможно, вы не сможете воспроизвести именно этот вывод):

  $ ghc --show-iface Goaf.hi

…

142c0e92c650162b33735c798cb20be3
  $ winlining0 :: Int # -> Int #
  {- Arity: 1, HasNoCafRefs, Strictness: , Inline: [0] -}
e447f016aa264b71f156911b664944d0
  inlining0 :: Int -> Int
  {- Арность: 1, HasNoCafRefs, Строгость:  m,
     Встроенный: INLINE [0],
     Развертывание: InlineRule (1, True, False)
                (\ (w :: Int) ->
                 case w of ww {I # ww1 ->
                 case $ winlining0 ww1 из ww2 {ПО УМОЛЧАНИЮ -> I # ww2}}) -}

…  

Эта $ winlining0 на самом деле является скомпилированной функцией, которая работает с распакованными целыми числами Int # и не является встроенной. inlining0 сам по себе представляет собой тонкую оболочку, которая превращает результат типа Int # в нормальный Int , помещая его в конструктор Int I # . Я не буду вдаваться в подробные объяснения неупакованных данных и примитивов, но Int # — это просто ваш трудолюбивый C int с голым железом, а Int — это наш знакомый упакованный ленивый Haskell Int (там ссылки на примитивный Haskell в конце руководства, вы можете начать там форму, если вам это интересно).

Здесь мы видим две важные вещи:

  • inlining0 сам (в виде $ winlining0 ) не выгружается в файл интерфейса, это означает, что мы потеряли возможность заглянуть внутрь него.

  • Тем не менее, надежда умирает напоследок даже для GHC, поэтому он превратил функцию inlining0 в оболочку, которая, как видите, является встроенной. Идея состоит в том, что если inlining0 вызывается в арифметическом контексте с некоторыми другими операциями на Int s, GHC может быть в состоянии оптимизировать дальше и лучше склеивать вещи, работающие на Int # s (например, $ winlining0 ) вместе .

Теперь воспользуемся прагмой INLINEABLE (если вы следите за экспериментами самостоятельно, не забудьте также экспортировать новую функцию):

  inlining1 :: Int -> Int
inlining1 x =
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000]
{- # INLINEABLE inlining1 # -}  

… что дает:

 …

033f89de148ece86b9e431dfcd7dde8c
  $ winlining1 :: Int # -> Int #
  {- Arity: 1, HasNoCafRefs, Strictness: , Inline: INLINABLE [0],
     Развертывание:  (\ (ww :: Int #) ->

       … Много чего…

6a60cad1d71ad9dfde046c97c2b6f2e9
  inlining1 :: Int -> Int
  {- Арность: 1, HasNoCafRefs, Строгость:  m,
     Встроенный: INLINE [0],
     Развертывание: InlineRule (1, True, False)
                (\ (w :: Int) ->
                 case w of ww {I # ww1 ->
                 case $ winlining1 ww1 из ww2 {ПО УМОЛЧАНИЮ -> I # ww2}}) -}  

Результат почти такой же, но теперь у нас есть полное разворачивание $ winlining1 в нашем файле интерфейса.Маловероятно, что это значительно улучшит производительность, потому что наши функции довольно медленные, одноразовые звери и встраивание здесь действительно не имеют большого значения:

  бенчмаркинг inlining0
время 5,653 мс (5,632 мс .. 5,673 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее 5,614 мс (5,601 мс .. 5,627 мс)
стандартное отклонение 39,86 мкс (33,20 мкс .. 48,70 мкс)

бенчмаркинг inlining1
время 5,455 мс (5,442 мс .. 5,471 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее значение 5,447 мс (5,432 мс .. 5,458 мс)
стандартное отклонение 38,08 мкс (28,36 мкс .. 58,38 мкс)  

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

Оказывается, не только встраивание требует доступа к исходному телу функции для работы, но и некоторые другие оптимизации тоже, поэтому прагма INLINEABLE , вызывающая развертывание функции в файле интерфейса , эффективно удаляет границы модуля, которые в противном случае могли бы помешать другие оптимизации от применения.Мы увидим, как это работает со специализацией, в следующем разделе. По этой причине нет ничего необычного в том, что INLINEABLE используется в саморекурсивной функции, потому что намерение состоит не во встраивании функции, а в том, чтобы выгрузить ее определение в файл интерфейса.

Более простой подход к встраиванию управления — использование прагмы INLINE . Когда GHC вычисляет вес функции, эта прагма заставляет функцию казаться очень легкой до такой степени, что GHC всегда решает встроить ее.Итак, {- # INLINE myFunction # -} вызовет безусловное встраивание myFunction везде (за исключением крайних случаев, например, когда myFunction является саморекурсивной).

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

Давайте рассмотрим пример из реального практического пакета под названием http-client-tls , который добавляет поддержку TLS (HTTPS) в другой пакет ( http-client ) для выполнения HTTP-запросов.В пакете есть понятие HTTP-менеджера, в котором хранится информация об открытых соединениях и тому подобное. Проблема в том, что его создавать дорого, и, как правило, у вас должен быть только один такой менеджер для максимального разделения соединения. Для этого есть вещь под названием globalManager , которую вы можете получить и установить, когда вы находитесь в монаде IO (она использует IORef под капотом). Чтобы получить IORef глобального менеджера, используется следующий код (ну, не совсем, это упрощено, но когда-нибудь он может перейти в эту форму):

  globalManager :: Менеджер IORef
globalManager =
  unsafePerformIO (newManager tlsManagerSettings >> = newIORef)
{- # NOINLINE globalManager # -}  

Здесь мы используем unsafePerformIO , который имеет тип IO a -> a , и его опасно добавлять в ваш код, если вы не знаете, что делаете.По сути, он просто делает свою грязную штуку IO средь бела дня, но делает вид, что это благоразумно и чисто, не желая жить в клетке IO . Нам нужен IORef , а не IO IORef , поскольку последний — всего лишь рецепт того, как получить IORef еще одному такому менеджеру, и мы хотим, чтобы он был создан только один раз. Выражение внутри unsafePerformIO должно быть запущено, и после этого его результат должен быть передан для использования в будущем. Что ж, он будет разделен нормально, поскольку значение имеет имя и верхний уровень, но одна вещь может помешать нашему успеху: GHC может просто встроить его, вызывая повторное создание и проблемы с совместным использованием соединения, которых мы хотели избежать вначале. место.Чтобы исправить это, мы добавляем прагму NOINLINE , больше не обращая внимания на последствия небезопасного отношения globalManager .

Другой вариант использования NOINLINE более очевиден. Помните, что GHC не оптимизирует тело встроенной функции? Если вам все равно, будет ли какая-то функция myFunction встроена или нет, но вы хотите, чтобы ее тело было оптимизировано, вы можете решить проблему следующим образом:

  myFunction :: Int -> Int
myFunction =…
{- # NOINLINE myFunction # -}  

Часто бывает также необходимо, , запретить встраивание, пока не произойдет какая-то другая оптимизация .Это также делается с NOINLINE и INLINE , но для управления порядком применения оптимизаций нам нужно будет освоить больше черной магии, чем мы знаем сейчас, поэтому давайте перейдем к специализации.

Специализированная

Чтобы понять, как работает специализация (и что это такое, если на то пошло), нам сначала нужно рассмотреть, как специальный полиморфизм с классами типов реализован в GHC. Когда в сигнатуре функции есть ограничение класса типа:

  foo :: Num a => a -> a
foo =…  

… это означает, что функция должна работать по-разному для разных и , реализующих класс типа ( Num в нашем примере).Это достигается путем передачи словаря, проиндексированного методами в данном классе типов, поэтому приведенный выше пример превращается в:

  foo :: Num a -> a -> a
foo d =…  

Обратите внимание на аргумент d типа Num a . Это словарь, содержащий функции, реализующие методы класса типа Num . Когда необходимо вызвать метод этого класса, словарь индексируется по имени этого метода, и используется извлеченная функция. foo не только принимает словарь в качестве дополнительного аргумента, но также передает его полиморфным функциям внутри foo , и эти функции могут передавать его функциям в своих телах:

  foo :: Num a -> a -> a
foo d =… bar d…
  где
    bar, baz :: Num a -> a -> a
    bar d =… baz d…
    баз d =…  

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

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

  • Модуль экспортирует полиморфную функцию.Для специализации нам нужно тело функции, но в этом случае у нас есть только скомпилированная версия функции, поэтому мы просто используем ее без специализации. Решение состоит в том, чтобы использовать INLINEABLE в экспортированной полиморфной функции в сочетании с SPECIALIZE в модуле, в котором мы хотим специализировать функцию (см. Ниже).

Итак, если вы хотите специализироваться, ваш инструмент — это прагма SPECIALIZE . Синтаксически прагму SPECIALIZE можно поместить везде, где может быть указана ее сигнатура типа:

  foo :: Num a => a -> a
foo =…
{- # SPECIALIZE foo :: Int -> Int # -}  

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

  {- # SPECIALIZE f ::  # -}  

… действительно, когда

  f_spec :: <тип>
f_spec = f  

… действительно. Это имеет смысл!

Фактический эффект прагмы заключается в создании специализированной версии указанной функции и правила перезаписи (они описаны в разделе о правилах перезаписи ниже с более подробной информацией о том, как работает SPECIALIZE ), которые перезаписывают вызовы исходной функции на вызывает свою специализированную версию всякий раз, когда типы совпадают.

Существует способ специализации всех методов в классе типов для конкретных экземпляров этого класса. Выглядит это так (пример из руководства пользователя GHC):

  instance (Eq a) => Eq (Foo a), где
  {- # SPECIALIZE instance Eq (Foo [(Int, Bar)]) # -}
  … Обычные вещи…  

Также возможно встроить специализированную версию функции (обычная специализация отключает встраивание, как будет продемонстрировано позже в руководстве) с помощью прагмы SPECIALIZE INLINE .Это может быть удивительно, но он будет работать даже с саморекурсивными функциями. Мотивация здесь заключается в том, что полиморфная функция, в отличие от функции, которая работает с конкретными типами, может фактически использовать разные экземпляры, когда она вызывается в разных контекстах, поэтому встраивание специализированных версий функции не обязательно расходится. Очевидным следствием этого является то, что GHC также может зайти в бесконечный цикл, так что будьте осторожны. Также доступен вариант SPECIALIZE NOINLINE .

Для практического примера попробуем начать с этого кода:

  модуль Goaf
  (special0 '
  , специальный0)
где

special0 ':: (Num a, Enum a) => a -> a
special0 'x =
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000]

special0 :: Int -> Int
special0 x = special0 'x `rem` 10  

В интерфейсном файле получаем:

 …

3d2b7aef38f4af3a87867079a7fb9d7d
  $ w $ sspecial0 ':: Int # -> Int #
  {- Arity: 1, HasNoCafRefs, Strictness: , Inline: [0] -}

9aab4f68c56ea324d5b4f1ae96f44304
  special0 :: Int -> Int
  {- Арность: 1, HasNoCafRefs, Строгость:  m,
     Развертывание: InlineRule (1, True, False)
                (\ (x :: Int) ->
                 case special0_ $ sspecial0 'x of wild2 {I # x1 ->
                 I # (remInt # x1 10 #)}) -}
97c360215ea1cab7acdf5a4928d349e8
  special0 ':: (Num a, Enum a) => a -> a
  {- Arity: 3, HasNoCafRefs,
     Строгость:    -}
efc0709eeb0afdb2be8cdce06cc54623
  special0_ $ sspecial0 ':: Int -> Int
  {- Арность: 1, HasNoCafRefs, Строгость:  m,
     Встроенный: INLINE [0],
     Развертывание: InlineRule (1, True, False)
                (\ (w :: Int) ->
                 case w of ww {I # ww1 ->
                 case $ w $ sspecial0 'ww1 of ww2 {DEFAULT -> I # ww2}}) -}
"SPEC special0 '@ Int" [ВСЕГДА] для всех ($ dNum :: Num Int)
                                       ($ dEnum :: Enum Int)
  special0 '@ Int $ dNum $ dEnum = special0_ $ sspecial0'  

Что я могу сказать, GHC действительно хорош в специализации, если полиморфная функция определена и используется в том же модуле.Я не мог найти случая, когда GHC 8.0.1 не смог бы специализироваться самостоятельно, браво! Специализированная версия special0 ' здесь называется $ w $ sspecial0' и работает на Int # для максимальной скорости.

Что еще мы видим? special0 ' компилируется, но не выгружается в файл интерфейса. Это означает, что если мы используем его из другого модуля, мы должны получить значительно худшую производительность по сравнению с special0 , давайте попробуем:

  специальные тесты0
время 5.457 мс (5,436 мс .. 5,477 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее 5,481 мс (5,470 мс .. 5,492 мс)
стандартное отклонение 35,69 мкс (29,94 мкс .. 44,88 мкс)

бенчмаркинг special0_alt <---- определен в отдельном модуле
время 5,462 мс (5,436 мс .. 5,496 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее 5,472 мс (5,458 мс .. 5,485 мс)
стандартное отклонение 41,42 мкс (33,29 мкс .. 55,02 мкс)  

Хм? В чем дело? special0_alt также смог воспользоваться специализированной функцией $ w $ sspecial0 '! Но если мы удалим экспорт special0 , все изменится, поскольку special0_alt больше не сможет найти соответствующую специализацию (она не будет сгенерирована GHC):

  сравнительный анализ special0_alt
время 912.0 мс (866,2 мс .. 947,7 мс)
                     1.000 R² (NaN R2 .. 1.000 R²)
среднее значение 931,0 мс (919,8 мс .. 939,9 мс)
стандартное отклонение 13,88 мс (0,0 с .. 15,45 мс)  

Ой черт, на × 167 замедление нехорошее. Попробуем исправить:

  special0 ':: (Num a, Enum a) => a -> a
special0 'x =
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000]
{- # SPECIALIZE special0 ':: Int -> Int # -}  

Это возвращает нашу специализацию:

  special0 '_ $ sspecial0' :: Int -> Int
  {- Арность: 1, HasNoCafRefs, Строгость:  m,
     Встроенный: INLINE [0],
     Развертывание: InlineRule (1, True, False)
                (\ (w :: Int) ->
                 case w of ww {I # ww1 ->
                 case $ w $ sspecial0 'ww1 of ww2 {DEFAULT -> I # ww2}}) -}
"SPEC special0 '" [ВСЕГДА] для всех ($ dNum :: Num Int)
                                 ($ dEnum :: Enum Int)
  special0 '@ Int $ dNum $ dEnum = special0' _ $ sspecial0 ' 

… и он действительно возвращает special0_alt свою способность работать хорошо:

  сравнительный анализ special0_alt
время 5.392 мс (5,381 мс .. 5,403 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее 5,399 мс (5,392 мс .. 5,408 мс)
стандартное отклонение 25,12 мкс (16,60 мкс .. 38,90 мкс)  

Подведем итог:

  • У GHC нет проблем со специализацией для вас, когда полиморфная функция используется в том же модуле, который он определил: у нее есть тело и она знает, что делать.

  • Отсутствие специализации убивает вашу производительность полностью и очень надежно.

  • Если вы можете угадать, какие специализации запрашивать у GHC при написании модуля, в большинстве случаев вы в порядке и можете вернуть свою скорость.

А как насчет случая, когда пользователю библиотеки нужна специализация, о которой автор библиотеки не подумал? Посмотрим. Сначала мы удаляем прагму SPECIALIZE для special0 ', чтобы при компиляции нашего «исходного» модуля не генерировались никакие специализации. Тогда мы стараемся специализироваться на модуле «потребительский»:

  special0_alt :: Int -> Int
special0_alt x = special0 'x `rem` 10
{- # SPECIALIZE special0 ':: Int -> Int # -}  

… и GHC говорит нам простым языком:

Вы не можете СПЕЦИАЛИЗИРОВАТЬ special0 ', потому что в его определении нет прагмы INLINE / INLINABLE

(или его определяющий модуль Goaf был скомпилирован без -O)

Круто, но мы это уже знаем, не так ли? Давайте добавим тело special0 ' к файлу интерфейса с INLINEABLE :

  special0 ':: (Num a, Enum a) => a -> a
special0 'x =
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000] +
  продукт [x..1000000]
{- # INLINEABLE special0 '# -}  

… и мы снова выигрываем:

  сравнительный анализ special0_alt
время 5,329 мс (5,313 мс .. 5,348 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее 5,340 мс (5,326 мс .. 5,356 мс)
стандартное отклонение 45,16 мкс (36,56 мкс .. 55,29 мкс)  

У меня также было другое предупреждение от GHC, когда я использовал ту же комбинацию INLINEABLE / SPECIALIZE :

Прагма

SPECIALIZE, вероятно, не сработает для встроенной функции foo

… и тесты показали, что он действительно не сработал.Итак, да, будьте осторожны и не забывайте проводить тесты каждый раз, когда что-то меняете!

Правила перезаписи

Haskell, будучи чистым языком, дает GHC волшебную способность выполнять широкий спектр преобразований над программами Haskell без изменения их значения. И GHC позволяет программисту принять участие в этом процессе. Спасибо, GHC!

Правила pragma

Прагма RULES позволяет писать произвольные правила преобразования определенных комбинаций функций.Вот пример использования ПРАВИЛ :

  {- # ПРАВИЛА
"карта / карта" для всех f g xs. карта f (карта g xs) = карта (f. g) xs
  # -}  

Давайте рассмотрим пример, объясняющий его синтаксис (руководство пользователя GHC имеет очень хорошее описание, поэтому я не понимаю, почему бы не включить его здесь почти дословно):

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

  • Закрытие # -} должно начинаться в столбце справа от открытия {- # . Довольно странное требование, кстати. Если вы знаете, почему это так, прокомментируйте.

  • У каждого правила есть имя, заключенное в двойные кавычки. Само название вообще не имеет значения. Он используется только при сообщении количества срабатываний правила.

  • Каждая переменная, упомянутая в правиле, должна либо находиться в области действия (например, , карта ), либо быть связана с для всех (например,грамм. f , g , xs ). Переменные, связанные на всех , называются переменными шаблона . Они разделены пробелами, как в случае типа для всех .

  • Переменная шаблона может дополнительно иметь сигнатуру типа. Если тип переменной шаблона является полиморфным, она должна иметь сигнатуру типа . Например:

      {- # ПРАВИЛА
    "сложить / построить" для всех k z (g :: forall b. (a -> b -> b) -> b -> b).foldr k z (построить g) = g k z
      # -}  

    Поскольку g имеет полиморфный тип, он должен иметь сигнатуру типа.

  • Левая часть правила должна состоять из переменной верхнего уровня, применяемой к произвольным выражениям. Например, это не нормально:

      {- # ПРАВИЛА
    "неправильно1" для всех e1 e2. case True of {True -> e1; Ложь -> e2} = e1
    "неправильно2" для всех f. f Истина = Истина
      # -}  

    В "неправильный1" LHS не является приложением; в "неправильно2" LHS имеет переменную шаблона в заголовке.

  • Правило не обязательно должно находиться в том же модуле, что и (любая из) переменные, которые оно упоминает, хотя, конечно, они должны быть в области действия.

  • Все правила неявно экспортируются из модуля и, следовательно, действуют в любом модуле, который прямо или косвенно импортирует модуль, который определил правило. (То есть, если A импортирует B , который импортирует C , то правила C действуют при компиляции A .) Ситуация очень похожа на ситуацию с объявлениями экземпляра.

  • Внутри правила для всех обрабатывается как ключевое слово, независимо от любых других настроек флага. Кроме того, внутри правила автоматически включается языковое расширение -XScopedTypeVariables .

  • Как и другие прагмы, прагмы RULE всегда проверяются на наличие ошибок области и проверяются по типу. Проверка типов означает, что правая и левая части правила проверяются по типу и должны иметь один и тот же тип.

Затем руководство пользователя GHC объясняет, что на самом деле делают правила перезаписи (я немного его отредактировал):

GHC использует очень простой синтаксический алгоритм сопоставления для сопоставления правила LHS с выражением. Он ищет подстановку, которая делает LHS и выражение синтаксически равными по модулю альфа-преобразования (то есть правило совпадает только в том случае, если типы совпадают). Если необходимо, шаблон (правило), но не выражение, расширяется. (Расширение этого выражения может привести к ошибкам лени.) Но бета-преобразование не выполняется (это называется сопоставлением более высокого порядка).

Это требование дословного сопоставления по модулю альфа-преобразования в сочетании с тем фактом, что в процессе оптимизации в GHC происходит много всего, немного усложняет работу с правилами. То есть иногда правила не срабатывают. Некоторые случаи этого рассматриваются в следующем разделе, который называется «Попутно».

Еще одна важная вещь, которую следует упомянуть, это то, что когда несколько правил совпадают одновременно, GHC произвольно выберет одно для применения.Вы можете спросить: «Почему бы не выбрать первый, например?» - ну, учитывая, что правила очень похожи на объявления экземпляров в отношении того, как они импортируются, для них нет порядка, и единственное, что GHC может сделать, когда несколько соответствие правил - это либо не применять ничего (вероятно, это хуже, чем применять хоть что-то), либо выбрать случайным образом и применить его.

Теперь, прежде чем мы начнем рассматривать проблемы, которые могут возникнуть у вас с RULES , я обещал показать, какие правила генерирует прагма SPECIALIZE .Вот их:

  foo :: Num a => a -> a
foo =…
{- # СПЕЦИАЛИЗИРУЙТЕ foo :: Int -> Int # -}

⇒

fooForInts :: Int -> Int - генерируется GHC
fooForInts =…
{- # NOINLINE foo # -}
{- # ПРАВИЛА "foo for ints" foo = fooForInts # -}  

Да, специализация обычно «отключает» встраивание. Подумайте об этом: мы создали специализированную версию функции и у нас есть правило, которое заменяет полиморфную функцию foo на fooForInts , поэтому мы не хотим, чтобы foo был встроен, потому что тогда у правила не будет никаких шансов стрелять!

Попался

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

GHC не пытается проверить, имеет ли RHS то же значение, что и LHS . Ответственность за то, чтобы правила не наносили ущерба, лежит на программисте! Примером хитрого правила, которое может показаться очевидным, может быть что-то вроде этого:

  {- # ПРАВИЛА
"двойной реверс" на все хз. обратный (обратный хз) = хз
  # -}  

На первый взгляд это действительно имеет смысл, не так ли? Тем не менее, правило «двойной реверс» не сохраняет значение выражения, которое оно трансформирует. reverse (reverse xs) , примененный к бесконечному списку, будет расходиться, никогда не давая никаких элементов, в то время как бесконечный список xs можно использовать нормально, учитывая, что он никогда не принудительно используется полностью.

GHC не пытается гарантировать, что правила завершаются . Например (пример из руководства пользователя GHC):

  {- # ПРАВИЛА
"петля" на все x y. f x y = f y x
  # -}  

… заставит компилятор зайти в бесконечный цикл.

Чтобы сделать вещи более интересными для программиста, не только каждое преобразование не должно приводить к различиям в значении, возможности завершения и т. Д., но также в сложных комбинациях функций желательно, чтобы мы получали один и тот же результат независимо от того, где мы начинаем преобразование, с условием, что мы применяем правила до тех пор, пока никакие правила больше не будут применяться - это называется слиянием . Вот пример, который, мы надеемся, продемонстрирует, что имеется в виду (адаптированный из примера, найденного в Haskell Wiki):

  {- # ПРАВИЛА
"f / f" для всех x. f (f x) = f x
"f / g" для всех x. f (g x) = fg x
  # -}  

Правило "f / f" гласит, что f является своего рода идемпотентной функцией, тогда как правило "f / g" распознает конкретную комбинацию f и g и заменяет ее на ad- реализация hoc fg .

Теперь рассмотрим перезапись f. f. г . Если мы сначала применим "f / f" , тогда мы получим fg x , но если мы сначала применим "f / g" , то мы получим f. fg . Система не сливная. Очевидным решением было бы добавить это правило:

  {- # ПРАВИЛА
"f / fg" для всех x. f (fg x) = fg x
  # -}  

… что делает систему единой. GHC не пытается проверить, совпадают ли ваши правила , поэтому потратьте некоторое время, чтобы проверить и свой набор правил на совпадение!

Наконец, сопоставление правил записи в методах классов типов бесполезно , потому что методы могут быть специализированы (то есть заменены специализированными менее полиморфными функциями, генерируемыми «на лету») с помощью GHC до того, как правила перезаписи смогут быть применены , поэтому такие правила наверняка не сработают, потому что типы специализированных функций не будут соответствовать типам, указанным в правилах перезаписи.

Наконец, хотя встраивание может мешать правилам перезаписи, оно также может помочь склеить вместе различные части кода, действуя как катализатор химической реакции правил перезаписи. Для прагмы INLINE есть специальный модификатор, называемый CONLIKE , который сообщает GHC: «Эй, если встраивание этого правила (даже много раз) помогает некоторым правилам перезаписи срабатывать, переходить в режим безумия и встраиваться, это достаточно дешево для нас». CONLIKE означает «похожий на конструктор». Фактически, GHC поддерживает инвариант, согласно которому каждое приложение-конструктор имеет аргументы, которые можно дублировать бесплатно: переменные, литералы и типы приложений (вы можете найти больше об этом в «Секретах инлайнера GHC», см. Ссылки для дальнейшего чтения на конец учебника), отсюда и название.

Фазовое управление

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

GHC имеет концепцию упрощенных фаз. Фазы пронумерованы. Первая фаза, которая выполняется в настоящее время, имеет номер 4 (возможно, их будет больше в более поздних версиях GHC), затем идет номер 3, 2, 1 и, наконец, последняя фаза имеет номер 0.

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

  1. Укажите, начиная с какой фазы следует включить данное правило перезаписи или встроенную / специализированную директиву.

  2. Укажите, до какой фазы (не включая) правило должно быть включено.

Синтаксическая часть сводится к добавлению [n] или [~ n] после имени прагмы. В руководстве пользователя GHC есть действительно хорошая таблица, которая нам обязательно нужна здесь:

  - До фазы 2, фазы 2 и позже
{- # INLINE [2] f # -} - Нет Да
{- # INLINE [~ 2] f # -} - Да Нет
{- # NOINLINE [2] f # -} - Нет, возможно
{- # NOINLINE [~ 2] f # -} - Может быть, нет

{- # INLINE f # -} - Да Да
{- # NOINLINE f # -} - № №  

Относительно «может быть»:

Под «Может быть» мы подразумеваем, что применяются обычные правила эвристического встраивания (если тело функции маленькое, или оно применяется к интересным на вид аргументам и т. Д.).

Управление фазой также доступно для SPECIALIZE и для отдельных правил в RULES . Давайте посмотрим, какой вид индикации фазы эффекта имеет прагма SPECIALIZE , например:

  foo :: Num a => a -> a
foo =…
{- # SPECIALIZE [1] foo :: Int -> Int # -}

⇒

fooForInts :: Int -> Int - генерируется GHC
fooForInts =…
{- # NOINLINE [1] foo # -}
{- # ПРАВИЛА [1] foo = forForInts # -}  

Здесь индикация фазы для SPECIALIZE имеет эффект отключения встраивания до тех пор, пока не придет время активировать «правило специализации».

В качестве примера того, как контроль фазы может быть незаменим при использовании правил перезаписи, достаточно взглянуть на правила map , определенные в Prelude :

  - Правила для карты работают так.
-
- До (но не включая) фазы 1 мы используем правило «сопоставления», чтобы
- переписать все насыщенные приложения карты с ее сборкой / сворачиванием
- форма, надеясь, что произойдет слияние.
- На этапах 1 и 0 мы отключаем это правило, встроенную сборку и
- включить правило "mapList", которое перезаписывает foldr / mapFB
- превратилась в обычную карту.-
- Важно, чтобы эти два правила не действовали одновременно.
- (вместе с разворачиванием сборки) иначе получился бы бесконечный цикл
- в правилах. Отсюда и управление активацией ниже.
-
- Правило «mapFB» оптимизирует составы карты.
-
- Этому же шаблону следуют многие другие функции:
- например, добавить, фильтровать, повторять, повторять и т. д.

{- # ПРАВИЛ
"карта" [~ 1] для всех функций. карта f xs = build (\ c n -> foldr (mapFB c f) n xs)
"mapList" [1] для всех f. foldr (mapFB (:) f) [] = map f
"mapFB" для всех c f g.mapFB (mapFB c f) g = mapFB c (f.g)
  # -}  

Обратите внимание на два важных момента:

  1. Без управления фазой оба правила «map» и «mapList» были бы активны сразу, и GHC перешел бы в бесконечный цикл. Фазовый контроль - единственный способ заставить этот набор правил работать.

  2. Сначала мы используем правило «map» , а затем используем «mapList» , которое по существу перезаписывает функцию обратно в ее форму map .Эта стратегия называется «парными правилами». Смысл здесь в том, чтобы попытаться представить функцию в удобной для слияния форме, но если к тому времени, когда мы намекаем, что слияние фазы 1 все еще не произошло, лучше переписать ее обратно.

    Может быть неочевидно, как результат "map" будет соответствовать правилам "mapList" , но если вы помните определение build g = g (:) [] и тот факт, что что он наверняка будет встроен в фазу 1, тогда "mapList" должен иметь смысл.

Вы могли подумать: «Fusion? Еще одно модное слово в нескончаемом словаре Haskell? ». Это подводит нас к следующей важной теме этого руководства…

Фьюжн

Хватит этой волосатой дряни! Сделайте глубокий вдох, давайте теперь обсудим кое-что другое. Этот раздел будет о слиянии, но прежде чем мы начнем об этом говорить, нам нужно определить, что такое «слияние».

Для целей этого руководства слияние - это метод, который позволяет избежать построения промежуточных результатов (таких как списки, векторы, массивы…) при связывании операций (функций).Выделение промежуточных результатов может действительно лишить вашу программу энергии, поэтому в некоторых случаях слияние является очень хорошим методом оптимизации.

Чтобы продемонстрировать преимущества слияния, достаточно начать с простой композиции функций, которую вы, возможно, пишете довольно часто. Единственная разница в том, что мы будем использовать наши собственные, самодельные функции (функции из Prelude имеют правила перезаписи, которые нам еще предстоит изобретать), реализованные так, как вы и ожидали:

  map0 :: (a -> b) -> [a] -> [b]
map0 _ [] = []
map0 f (x: xs) = f x: map0 f xs

foldr0 :: (a -> b -> b) -> b -> [a] -> b
foldr0 _ b [] = b
foldr0 f b (a: as) = ​​foldr0 f (f a b) при

nofusion0 :: [Int] -> Int
nofusion0 = foldr0 (+) 0.map0 sqr

sqr :: Int -> Int
sqr х = х * х  

Все это выглядит довольно обыденно - старый добрый конвейер функций с композицией функций, вы наверняка напишете много такого кода. Посмотрим, как это работает:

  сравнительный анализ nofusion0
время 155,4 мс (146,4 мс .. 162,4 мс)
                     0,996 R² (0,980 R² .. 1.000 R²)
среднее значение 155,1 мс (151,3 мс .. 159,0 мс)
std dev 5,522 мс (3,154 мс .. 7,537 мс)  

Это результат с [0..1000000] передано как аргумент nofusion0 .

С весят (относительно новая библиотека, позволяющая узнать потребление памяти вашим кодом) получаю следующее:

  Case Bytes Проверка GC
nofusion0 249,259,656 448 ОК  

В ленивом языке, таком как Haskell, лень просто меняется, когда выделяются части промежуточных списков, но они все равно должны выделяться, потому что это то, что следующий шаг в «конвейере» принимает в качестве входных данных, и это накладные расходы, которые мы хотим уменьшить с помощью слияния.

Можем ли мы добиться большего, если перепишем все как одну функцию, которая суммирует и умножает за один проход? Не так уж и сексуально, но давайте попробуем увидеть, какой мощности нам не хватает с нашей «элегантной» композицией функций обработки списков:

  вручную Fused :: [Int] -> Int
manualFused [] = 0
вручную Fused (x: xs) = x * x + вручную Fused xs  

Давайте посмотрим на это:

  тестирование вручную Fused
время 17,10 мс (16.71 мс .. 17,54 мс)
                     0,996 R² (0,992 R² .. 0,998 R²)
среднее 17,18 мс (16,87 мс .. 17,62 мс)
std dev 932,8 мкс (673,7 мкс .. 1,453 мс)

Проверка GC байтов регистра
с ручным предохранителем 96,646,160 153 OK  

Улучшение разительное. Мы просто вручную «объединили» две функции и создали код, который работает быстрее, потребляет меньше памяти и делает то же самое. Но как отказаться от компоновки и элегантности - основных достоинств функционального программирования? Ни за что!

Мы хотели бы достичь следующего:

  1. Умение писать красивые компонуемые программы.
  2. По возможности избегайте распределения промежуточных результатов, потому что это отстой.

Пункт 2 может (и был) адресован по-другому:

  1. Мы можем построить наш словарь маленьких «примитивных» операций (которые мы используем в качестве строительных блоков в наших программах) таким образом, чтобы они никогда не давали результатов сразу. Поэтому, когда такие примитивы объединяются, они создают другую (обернутую) функцию, которая также не дает немедленного результата. Чтобы получить «настоящий» результат, нам нужна еще одна функция, которая может «запускать» созданное нами составное действие.Это тоже слияние, и так, например, работает пакет repa .

  2. Мы хотим съесть свой торт и съесть его. Мы можем открыть знакомый интерфейс, в котором каждый «примитив» дает результат немедленно, но мы также можем добавить правила перезаписи, которые (надеюсь) заставят GHC переписывать вещи таким образом, что в конце компилятор получит один жесткий цикл без промежуточных выделений.

Должен сказать, что мне больше нравится первый подход, потому что он более ясный и надежный.Посмотрим на это в действии.

Fusion без правил перезаписи

Возвращаясь к примеру с map и foldr , мы можем переписать функции по-другому, используя только что обсужденный принцип - избегая генерации промежуточных результатов. Для слияния важно, чтобы мы не писали наши функции как преобразования целых списков (или того, что у вас есть), потому что тогда мы в какой-то момент вернемся к проблеме создания этих списков.

На самом деле сложно иметь несколько независимых функций, которые концептуально работают со связанными списками без воссоздания структуры списка в той или иной форме.Итак, мы не будем начинать со слияния, которое работает со связными списками. Вместо этого давайте начнем с более очевидного примера: массивов.

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

  данных Массив a = Массив Int (Int -> a)

rangea :: Int -> Массив Int
rangea n = массив n id

mapa :: (a -> b) -> Массив a -> Массив b
mapa f (Размер массива g) = Размер массива (f. g)

foldra :: (a -> b -> b) -> b -> Массив a -> b
foldra f b (размер массива g) = go 0 b
  где
    идти п б '| n  Int
fuseda = фолдра (+) 0.mapa sqr. диапазон  

Здесь у нас есть то, что Repa называет «отложенными массивами». Обратите внимание, что функция rangea позволяет создавать массивы, элементы которых заполнены их индексами. Это сделано для простоты, в реальной библиотеке массивов мы хотели бы дополнить отложенные массивы реальными массивами, которые хранят все данные в памяти по смежным адресам и обеспечивают быструю индексацию, но для демонстрации слияния мы можем обойтись без «настоящего» массивы.

Теперь, если вы посмотрите на mapa , на самом деле он ничего не делает, кроме как немного усложняет функцию индексации, поэтому мы не создаем с ее помощью никаких промежуточных результатов. foldra позволяет обойти весь массив и получить некоторое значение, вычисленное из всех его элементов, в нашем случае он играет роль потребителя. Наконец, fuseda 1000000 - то же самое, что manualFused [0..1000000] , но работает намного быстрее.

Конечно, fuseda не эквивалентен по мощности ручному сплаву , но весь набор и функции показывают, что возможно комбинирование и скорость одновременно. Заметим еще раз, мы получаем это, просто изменяя функцию индексации, фактически ничего не делая с реальным массивом (который, конечно, может быть «визуализирован» или построен с использованием массива ).

Теперь попробуем проделать что-то подобное со связанными списками, хотя это менее очевидно. Мы должны начать с идеи не касаться реального списка, а изменить функцию, которая… что? Индексирует список? Что такая функция должна делать со списком? Если самая основная функция массива должна быть проиндексирована по положению его элементов, тогда какова самая основная функция списка? Как используется связанный список?

Если у нас есть список [a] , то он обычно потребляется через «несогласование», то есть мы берем начало списка a и получаем остальную часть [a] Data.List есть функция с именем cons , для этого давайте посмотрим на нее:

 cons :: [a] -> Может быть (a, [a])
CONS [] = Ничего
cons (a: as) = ​​Just (a, as)  

Итак, здесь мы можем получить заголовок данного списка и остальную его часть, но если данный список пуст, мы не можем получить его голову. Эту идею выражает Может быть, . Давайте попробуем представить «отложенный список» как оболочку для функции, подобной cons :

  newtype List a = List ([a] -> Maybe (a, [a]))  

Как насчет map и foldr ? Похоже, они довольно естественно вытекают из этого определения:

  map1 :: (a -> b) -> Список a -> Список b
map1 g (Список f) = Список h
  где
    h s '= case f s' из
      Ничего -> Ничего
      Просто (x, s '') -> Just (g x, s '')  

Угу.Это не проверка типа:

Не удалось сопоставить тип a с b

В чем проблема? Что ж, помните, что мы просто хотим сделать внутреннюю функцию более сложной. В данном конкретном случае это означает, что он должен использовать список типа [a] и создать список типа [b] , что означает, что внутренняя функция должна иметь тип [a] -> Maybe (b , [a]) (помните, мы производим элементы [b] по одному).Ясно, что эта сигнатура типа отличается от той, что у нас есть, поэтому мы должны ее скорректировать:

  newtype List a b = List ([a] -> Maybe (b, [a]))  

Итак, тип List a b означает «создает список элементов типа b из списка элементов типа a ». Не очень четкая подпись для такой вещи, как список, но давайте смиримся с этим и дойдем до конца, чтобы посмотреть, работает ли это хотя бы лучше. Наконец, map1 компилирует:

  map1 :: (a -> b) -> Список s a -> Список s b
map1 g (Список f) = Список h
  где
    h s '= case f s' из
      Ничего -> Ничего
      Просто (x, s '') -> Just (g x, s '')  

Подпись буквально гласит: «Когда у вас есть список, содержащий a элементов, независимо от того, что вы потребляете, чтобы получить их (в нашем случае это что-то, помеченное s ), я дам вам другой список, который дает b элементов, по-прежнему потребляющих то же самое s ”.

Давайте продолжим и реализуем foldr1 (это не ваш foldr1 из Predule , числовой суффикс просто показывает, к какому примеру он принадлежит). Для реализации foldr1 нам нужно что-то потреблять, потому что мы хотим получить в конце одно единственное значение - реальное значение, а не что-то «отложенное».

Мы могли бы передать источник значений напрямую в foldr1 , но это нехорошо по двум причинам:

  1. Мы хотим, чтобы подпись foldr1 оставалась как можно ближе к знакомой подписи foldr .

  2. foldr - это всего лишь один примитив, который «форсирует» отложенный список, а как насчет других? Стоит ли добавить ко всем им дополнительный аргумент? Это не элегантно.

Итак, что мы можем здесь сделать? Что ж, возможно, мы могли бы сохранить исходный список вместе с функцией, которая у нас уже есть [a] -> Maybe (b, [a]) :

  данных Список a b = Список ([a] -> Может быть (b, [a])) [a]  

Однако мы должны помнить, что мы хотим передать этот список без изменений, пока мы не хотим «принудительно» использовать этот список:

  map1 :: (a -> b) -> Список s a -> Список s b
map1 g (Список f s) = Список h s
- ^ ^
- | «Как есть» |
- + ----------- +
  где
    h s '= case f s' из
      Ничего -> Ничего
      Просто (x, s '') -> Just (g x, s '')

foldr1 :: (a -> b -> b) -> b -> Список s a -> b
foldr1 g b (Список f s) = go b s
  где
    go b 's' = case f s 'из
      Ничего -> b '
      Просто (x, s '') -> go (g x b ') s' ' 

Теперь, когда мы сохраняем исходный список в самом списке List , мы можем написать функцию, которая преобразует обычный список в отложенный:

  fromLinkedList :: [a] -> Список a a
fromLinkedList = Вывести список сообщений  

И просто для полноты картины вот как его вернуть:

  toLinkedList :: Список a b -> [b]
toLinkedList (Список f s) = развернуть f s  

Вот развертка из Data.Список :

  развернуть :: (s -> Может быть (a, s)) -> s -> [a]
развернуть f s = случай f s
  Ничего -> []
  Просто (x, s ') -> x: развернуть f s'  

развертка принимает начальное состояние, передает его заданной функции и получает один элемент окончательного списка и новое состояние. Это продолжается до Ничего не возвращается.

Наконец, мы можем построить fused1 , который решает ту же проблему суммирования списка чисел в квадрате:

  fused1 :: [Int] -> Int
плавленый1 = foldr1 (+) 0.map1 sqr. fromLinkedList  

Элегантность и сочетаемость: проверьте. Давайте протестируем его:

  сравнительный анализ fused1
время 3,422 мс (3,412 мс .. 3,433 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее 3,432 мс (3,427 мс .. 3,440 мс)
стандартное отклонение 19,74 мкс (14,15 мкс .. 29,65 мкс)

Проверка GC байтов регистра
плавленые1 80,000,016 153 ОК  

На данный момент это самая быстрая реализация! Что не так с нашим простодушным с ручным плавлением BTW? Разве это не должно быть самым быстрым? Ну, это не хвостовая рекурсия, но мы можем переписать это так:

  вручную Fused ':: [Int] -> Int
manualFused '= перейти 0
  где
    go! n [] = n
    go! n (x: xs) = go (n + x * x) xs  

И тогда он обязательно выиграет:

  тестирование вручную Fused '
время 3.206 мс (3,202 мс .. 3,210 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее 3,213 мс (3,210 мс .. 3,217 мс)
стандартное отклонение 11,28 мкс (7,599 мкс .. 17,46 мкс)

Проверка GC байтов регистра
ручная сварка '80,000,016 153 OK  

Возвращаясь к списку List , мы хотели бы удалить тип элементов, которые он потребляет. Я имею в виду, что если у вас есть список из a элементов, разве это не должно быть List a ? Конечно, должно.Давайте снова посмотрим определение Список :

  данных Список a b = Список ([a] -> Может быть (b, [a])) [a]  

[a] здесь на самом деле никогда не меняется в соответствии с нашей идеей не трогать его. Его тип должен быть точно таким же, как тип аргумента, который использует функция [a] -> Maybe (b, [a]) . Мы могли бы скрыть это тогда, используя экзистенциальную количественную оценку:

  Список данных b = для всех a. Список ([a] -> Может быть (b, [a])) [a]
≡ <через альфа-редукцию>
Список данных a = forall s.Список (s -> Maybe (a, s)) s  

При этом мы получаем следующие подписи (реализации остаются прежними):

  fromLinkedList :: [a] -> Список a
toLinkedList :: Список а -> [а]
map1 :: (a -> b) -> Список a -> Список b
foldr1 :: (a -> b -> b) -> b -> Список a -> b  

Намного лучше! Этот раздел продемонстрировал, что слияние выполнимо и прекрасно без правил перезаписи. В следующем разделе мы исследуем понятие «система слияния».

build / foldr система слияния

Другой подход к предотвращению промежуточных результатов можно резюмировать следующим образом: «мы можем использовать функции, которые работают с обычными списками, массивами, векторами и т. Д., И позволить GHC переписывать комбинации этих функций таким образом, чтобы мы по-прежнему получали один жесткий цикл. обработка всего за один проход ».

Вот здесь и вступают в игру правила перезаписи. Однако есть одна проблема с этим подходом: слишком много функций, которые нужно учитывать.Стандартный словарь функционального программиста включает в себя следующие специфичные для списка функции: map , filter , (++) , foldr , foldl , dropWhile и т. Д. Скажем оптимистично, мы хотим чтобы иметь возможность работать с 10 функциями, чтобы все они хорошо играли вместе и были переписаны GHC в высокопроизводительный код. Затем нужно учесть (как минимум!) 10 × 10 = 100 комбинаций этих функций. Теперь запомните все, что касается проверки того, что каждое преобразование является правильным, сливающимся, что нет комбинаций, которые отправляют GHC в бесконечный цикл и т. Д.Вы уже чувствуете боль?

Fusion с множеством различных функций - это сложно. Поэтому вместо этого мы хотели бы сделать следующее:

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

  2. Преобразуйте эти функции и упростите их комбинации вместо этого, используя (часто) только одно правило перезаписи.

В этом разделе мы рассмотрим систему слияния build / foldr , которая используется в пакете base и поддерживает все функции в списках, которые мы считаем само собой разумеющимися.

foldr - знакомая функция, но что такое build ? Это выглядит так:

  build :: (forall b. (A -> b -> b) -> b -> b) -> [a]
build g = g (:) []  

Что он делает? Зачем нам это нужно? Целью build является сохранение списка в «отложенной» форме путем абстрагирования по операции «cons» (:) и пустому списку [] «null». Аргументом функции является другая функция, которая принимает cons-подобную операцию (a -> b -> b) и нулевую «начальную точку» b и производит что-то того же типа b .Ясно, что это обобщение функций, производящих списковые вещи.

build просто дает этой общей функции конкретную функцию (:) для consing и [] в качестве отправной точки, и мы возвращаем наш список. Следующий пример взят из тезиса Дункана Куттса под названием «Stream Fusion: Практическое сокращенное слияние для типов коиндуктивных последовательностей» (да, название волосатое, но сам текст прост для понимания и довольно интересен, я рекомендую прочитать его полностью! ):

  сборка l == [1,2,3]
  где
    l cons nil = 1 `cons` (2` cons` (3 `cons` nil))  

Теперь система слияния с build и foldr имеет только одно правило:

  foldr f z (build g) = g f z  

Как это помогает исключить промежуточные списки? Давайте посмотрим, build g строит некоторый список, в то время как foldr fz проходит список, «заменяющий» (:) приложений на f и пустой список на z , на самом деле это популярное объяснение что делает foldr :

  foldr f z [1,2,3] = 1 `f` (2` f` (3 `f` z))  

Имея это в виду, g идеально подготовлен для прямого приема f и z , чтобы получить точно такой же результат!

Давайте перепишем наш пример, используя систему слияния build / foldr :

  map2 :: (a -> b) -> [a] -> [b]
map2 _ [] = []
карта2 f (x: xs) = f x: map2 f xs
{- # NOINLINE map2 # -}

{- # ПРАВИЛ
"map2" [~ 1] для всех файлов.map2 f xs = построить (\ c n -> foldr2 (mapFB c f) n xs)
"map2List" [1] для всех f. foldr2 (mapFB (:) f) [] = map2 f
"mapFB" для всех c f g. mapFB (mapFB c f) g = mapFB c (f. g)
  # -}

mapFB :: (b -> l -> l) -> (a -> b) -> a -> l -> l
mapFB c f = \ x ys -> c (f x) ys
{- # INLINE [0] mapFB # -}

foldr2 :: (a -> b -> b) -> b -> [a] -> b
foldr2 _ b [] = b
foldr2 f b (a: as) = ​​foldr2 f (f a b) при

{- # ПРАВИЛ
"build / foldr2" для всех f z (g :: forall b.(а -> б -> б) -> б -> б). foldr2 f z (построить g) = g f z
  # -}

fused2 :: [Int] -> Int
плавленый2 = foldr2 (+) 0. map2 sqr  

Несколько комментариев здесь:

  • Нам нужно NOINLINE на map2 , чтобы отключить предупреждение о том, что «map2» может никогда не сработать, потому что map2 может быть встроен первым. Конечно, map2 никогда не будет встроен, потому что он саморекурсивен, но GHC не может этого понять (пока).

  • mapFB - это вспомогательная функция, которая принимает функцию consing c , функцию, которую мы хотим применить к списку f , и голова лямбды внутри также связывает x , которое является входным значением (к нему применяется f . ) и ys , которая является остальной частью списка.LHS функции имеет только два аргумента для облегчения встраивания в нашем конкретном случае (см. Правила объединения). Конечно, мы хотим, чтобы он был встроен, но только в конце, потому что некоторые правила совпадают с ним, и они будут нарушены, если он будет встроен слишком рано.

  • Встраивание необходимо для всего этого, потому что оно объединяет отдельные фрагменты кода и позволяет GHC манипулировать ими как единым целым.

  • Правила перезаписи "map2" и "build / foldr2" нам уже знакомы. "mapFB" довольно банальный. Как я сказал ранее, здесь у нас есть то, что называется «парными правилами», то есть правило «map2List» переписывает все обратно к простому map2 , если слияние фазы 1 не произошло. Вот почему у нас есть нормальное определение для map2 , а не для build (…) one - если слияние не происходит, build / foldr вещи на самом деле только усугубляют ситуацию, поэтому это должно быть «вещь. попробовать »для компилятора, а не реализации по умолчанию.

Хорошо, посмотрим, действительно ли это что-то улучшит:

  тестирование fused2
время 107,5 мс (103,8 мс .. 110,2 мс)
                     0,998 R² (0,995 R² .. 1.000 R²)
среднее 107,3 ​​мс (104,6 мс .. 109,8 мс)
std dev 3,768 мс (2,519 мс .. 6,098 мс)

Проверка GC байтов регистра
плавленые2 161,259,568 310 ОК  

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

Как я уже сказал, встраивание очень важно в подобном бизнесе слияния. Обратите внимание, что foldr2 не будет встроена и не будет переписана (в отличие от map2 ). Это потому, что foldr является саморекурсивной. Сделаем его нерекурсивным и встроенным:

  foldr2 :: (a -> b -> b) -> b -> [a] -> b
foldr2 f z = go
  где
    go [] = z
    go (y: ys) = y `f` go ys
{- # INLINE [0] foldr2 # -}  

Мы указываем фазу 0, потому что хотим, чтобы GHC встроил ее, но только после того, как слияние произошло (помните, что если мы встроили его слишком рано, это нарушит наши правила слияния, и они не сработают).

Давайте попробуем еще раз:

  тестирование fused2
время 17,87 мс (17,48 мс .. 18,33 мс)
                     0,996 R² (0,992 R² .. 0,998 R²)
среднее 17,94 мс (17,61 мс .. 18,42 мс)
std dev 962,6 мкс (689,0 мкс .. 1,401 мс)

Проверка GC байтов регистра
плавленые2 96,646,160 153 ОК  

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

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

Действительно, большинство функций можно переписать с помощью foldr и build . Большинство, но не все. В частности, foldl и zip не могут быть эффективно объединены при записи через build и foldr . К сожалению, у нас нет возможности описать здесь все детали.Как уже упоминалось, диссертация Дункана Куттса - прекрасное чтение, если вы хотите узнать больше по этому поводу.

Stream fusion

Итак, мы знаем, что такое синтез, но вы, возможно, слышали о «потоковом синтезе». Слияние потоков - это метод слияния потоков. Что такое поток? Я считаю приемлемым описывать поток как список (концептуально), но без накладных расходов, которые обычно связаны со связанным списком.

Фактически, пытаясь объединить операции со списками, мы уже разработали систему объединения потоков! Помните наше определение «отложенного списка»:

  Список данных a = forall s.Список (s -> Maybe (a, s)) s  

Список представляет так называемый «поток без пропуска». То есть мы можем либо получить элемент с этой моделью, либо закончить обработку, третьего варианта нет. Мы вернемся к этой проблеме «пропуска» позже в этом разделе, а пока давайте перепишем определение в более распространенной форме, прежде чем продолжить:

  поток данных a = для всех s. Stream (s -> Step a s) s

данные Шаг a s
  = Доходность a s
  | Выполнено  

Здесь ничего особо не изменилось, мы только что ввели тип данных Step , который совпадает с Maybe (a, s) .

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

Правило перезаписи, которое мы хотим использовать, чрезвычайно простое, намного проще, чем правило build / foldr . Помните, что мы можем превратить список в поток, например:

  stream :: [a] -> Stream a - он же fromLinkedList
stream = Поток f
  где
    f [] = Готово
    f (x: xs) = Доходность x xs  

… и мы можем вернуть наш список:

  unstream :: Stream a -> [a] - он же toLinkedList
unstream (Stream f s) = go s
  где
    go s '= case f s' из
      Готово -> []
      Доходность x s '' -> x: go s ''  

Тогда должно иметь смысл, что:

Преобразование из потока, а затем обратно в поток ничего не меняет.Если мы запишем наши функции как функции в потоках и обернем их в пару функций stream / unstream , мы должны получить функции, которые работают со списками:

  map3 :: (a -> b) -> [a] -> [b]
map3 f = не поток. map3 'f. ручей

map3 ':: (a -> b) -> Stream a -> Stream b
map3 'g (Stream f s) = Stream h s
  где
    h s '= case f s' из
      Готово -> Готово
      Доходность x s '' -> Доходность (g x) s ''

foldr3 :: (a -> b -> b) -> b -> [a] -> b
foldr3 f z = foldr3 'f z.ручей

foldr3 ':: (a -> b -> b) -> b -> Stream a -> b
foldr3 'g b (Stream f s) = go b s
  где
    go b 's' = case f s 'из
      Готово -> b '
      Урожайность x s '' -> go (g x b ') s' ' 

И одновременно с этим правилом перезаписи:

  {- # ПРАВИЛА
"поток / не поток" для всех (s :: Stream a). поток (unstream s) = s
  # -}  

GHC произведет промежуточные преобразования в термоусадочную пленку и обратно:

  fused3 :: [Int] -> Int
плавленый3 = foldr3 (+) 0.- | атакован правилом |
- + --------------- +
- ≡ foldr3 '(+) 0. map3 'sqr. поток  

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

Нам все еще нужно внести некоторые изменения, прежде чем это начнет работать. Сейчас я вижу следующее предупреждение:

Правило «поток / отключение» может никогда не сработать, потому что отключено от потока может быть встроено первым

Возможное исправление: добавьте прагму INLINE [n] или NOINLINE [n] для unstream

Я предлагаю вам скопировать код, который у нас есть для этой системы слияния потоков, и попробовать добавить некоторые прагмы, чтобы заставить ее работать.Затем вы можете сравнить его с решением, найденным в исходном коде этого руководства (см. Наше репо). Я написал туда несколько комментариев, чтобы объяснить, что я сделал и почему.

Вот результаты:

  сравнительный анализ fused3
время 3,450 мс (3,440 мс .. 3,459 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее 3,459 мс (3,450 мс .. 3,474 мс)
стандартное отклонение 34,64 мкс (23,78 мкс .. 52,99 мкс)

Проверка GC байтов регистра
плавленые3 80,000,016 153 ОК  

Итак, теперь у нас есть функции, которые работают с обычными списками, но их комбинации очень быстрые! Обратите внимание, что именно этот подход используется в популярных библиотеках, таких как вектор и текст .

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

  filter3 :: (a -> Bool) -> [a] -> [a]
filter3 f = unstream. filter3 'f. ручей

filter3 ':: (a -> Bool) -> Stream a -> Stream a
filter3 'p (Stream f s) = Stream g s
  где
    g s '= case f s' из
      Готово -> Готово
      Доходность x s '' ->
        если p x
          затем доходность x s ''
          иначе g s ''

fusedFilter :: [Int] -> Int
fusedFilter = foldr3 (+) 0.filter3 даже. map3 sqr  

Проблема здесь в том, что если нам нужно пропустить значение, единственное, что мы можем сделать, это рекурсивно вызвать g , что нехорошо, поскольку компилятор не может «сгладить», встроить и дополнительно оптимизировать рекурсивные функции. .

Бенчмаркинг показывает следующее:

  тестирование fusedFilter
время 10,79 мс (10,76 мс .. 10,82 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее значение 10,81 мс (10.79 мс .. 10,84 мс)
стандартное отклонение 69,54 мкс (47,87 мкс .. 118,8 мкс)

Проверка GC байтов регистра
fusedFilter 100,000,056 192 ОК  

Если мы введем Skip , g перестанет быть саморекурсивным (настройки других функций тривиальны и здесь не показаны):

  <…>

данные Шаг a s
  = Доходность a s
  | Пропустить
  | Выполнено

<…>

filter3 ':: (a -> Bool) -> Stream a -> Stream a
filter3 'p (Stream f s) = Stream g s
  где
    g s '= case f s' из
      Готово -> Готово
      Пропустить s '' -> Пропустить s ''
      Доходность x s '' ->
        если p x
          затем доходность x s ''
          else Пропустить s ''  

Это дает нам некоторые улучшения скорости и пространства:

  тестирование fusedFilter
время 8.904 мс (8,880 мс .. 8,926 мс)
                     1.000 кв.м (1.000 кв.м .. 1.000 кв.м)
среднее значение 8,923 мс (8,906 мс .. 8,941 мс)
стандартное отклонение 50,94 мкс (36,94 мкс .. 74,59 мкс)

Проверка GC байтов регистра
fusedFilter 80,000,016 153 ОК  

Появление Skip обещало славу и удачу, но в данном случае это не такое уж большое улучшение. Тем не менее, для максимальной эффективности предпочитайте слияние потоков с пропуском, но не пересекайте потоки!

Заключение

Мы узнали, как ускорить работу программ на Haskell с помощью магии прагм GHC и как избежать создания промежуточных результатов с помощью различных систем слияния.Теперь понимание внутренней работы таких пакетов, как base (функции списка с системой слияния build / foldr ), text и vector , не вызовет никаких проблем для образованного читателя 😀

В следующем разделе представлен рекомендуемый набор источников для тех, кто хочет узнать больше.

См. Также

Вот несколько ссылок о прагмах и слиянии GHC:

Если вам интересна эта тема, возможно, вы захотите узнать и о примитивах GHC:


Спасибо, что прочитали это руководство! Если у вас есть какие-либо отзывы, напишите нам в Твиттере или Facebook.Вы также можете открывать вопросы и запрашивать GitHub.


Добро пожаловать в Stack Builders!


Проверьте свою электронную почту, чтобы подтвердить подписку.

.

Добавить комментарий

Ваш адрес email не будет опубликован.