Go: математика / биты: библиотека для целочисленного битового тиддлинга

Созданный на 11 янв. 2017  ·  168Комментарии  ·  Источник: golang/go

Предыдущие обсуждения на https://github.com/golang/go/issues/17373 и https://github.com/golang/go/issues/10757.

Абстрактный

Это предложение вводит набор API для тидлинга целочисленных битов.

Фон

Это предложение вводит набор API для тидлинга целочисленных битов. Для этого предложения нас интересуют следующие функции:

  • ctz - считать завершающие нули.
  • clz - считать ведущие нули; log_2.
  • popcnt - подсчитать население; расстояние Хэмминга; целочисленная четность.
  • bswap - меняет порядок байтов на обратный.

Эти функции были выбраны путем опроса:

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

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

| пакет | clz | ctz | popcnt | bswap |
| --- | --- | --- | --- | --- |
| математика / большой | X | X | | |
| время выполнения / внутреннее / sys | X | X | | X |
| инструменты / контейнер / intsets | X | | X | |
| cmd / compile / internal / ssa | X | | X | |
| code.google.com/p/intmath | X | X | | |
| github.com/hideo55/go-popcount | | | X (asm) | |
| github.com/RoaringBitmap/roaring | | X | X (asm) | |
| github.com/tHinqa/bitset | X | X | X | |
| github.com/willf/bitset | X | | X (asm) | |
| gopl.io/ch2/popcount | | | X | |
| Встроенные функции GCC | X | X | X | X |

Многие другие пакеты реализуют подмножество этих функций:

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

| арка | clz | ctz | popcnt | bswap |
| --- | --- | --- | --- | --- |
| AMD64 | X | X | X | X |
| ARM | X | X | ? | X |
| ARM64 | X | X | ? | X |
| S390X | X | X | ? | X |

Все функции перестановки битов, кроме popcnt, уже реализованы в runtime / internal / sys и получают специальную поддержку от компилятора, чтобы «помочь добиться максимальной производительности». Однако поддержка компилятора ограничена пакетом времени выполнения, и другие пользователи Golang должны повторно реализовать более медленный вариант этих функций.

Предложение

Мы представляем новую библиотеку std math/bits со следующим внешним API, чтобы обеспечить оптимизированные для компилятора / оборудования реализации функций clz, ctz, popcnt и bswap.

package bits

// SwapBytes16 reverses the order of bytes in a 16-bit integer.
func SwapBytes16(uint16) uint16
// SwapBytes32 reverses the order of bytes in a 32-bit integer.
func SwapBytes32(uint32) uint32
// SwapBytes64 reverses the order of bytes in a 64-bit integer.
func SwapBytes64(uint64) uint64

// TrailingZeros32 counts the number of trailing zeros in a 32-bit integer, and if all are zero, then 32.
func TrailingZeros32(uint32) uint
// TrailingZeros64 counts the number of trailing zeros in a 64-bit integer, and if all are zero, then 64.
func TrailingZeros64(uint64) uint

// LeadingZeros32 counts the number of trailing zeros in a 32-bit integer, and if all are zero, then 32.
func LeadingZeros32(uint32) uint
// LeadingZeros64 counts the number of trailing zeros in a 64-bit integer, and if all are zero, then 64.
func LeadingZeros64(uint64) uint

// Ones32 counts the number of bits set in a 32-bit integer.
func Ones32(uint32) uint
// Ones64 counts the number of bits set in a 64-bit integer.
func Ones64(uint64) uint

Обоснование

Альтернативы этому предложению:

  • Функции битового тидлинга реализованы во внешней библиотеке, не поддерживаемой компилятором. Этот подход работает и является текущим положением дел. Среда выполнения использует методы, поддерживаемые компилятором, в то время как пользователи Golang продолжают использовать более медленные реализации.
  • Внешняя библиотека поддерживается компилятором. Учитывая, что мы ожидаем, что эта библиотека заменит runtime / internal / sys, это означает, что эта библиотека должна быть связана с компилятором и жить в стандартной библиотеке.

Совместимость

Это предложение не изменяет и не нарушает какой-либо существующий API stdlib и соответствует рекомендациям по совместимости.

Реализация

SwapBytes, TrailingZeros и LeadingZeros уже реализованы. Единственная отсутствующая функция - это Единицы, которые могут быть реализованы аналогично другим функциям. Если это предложение будет принято, его можно будет реализовать к Go1.9.

Открытые вопросы (если применимо)

Названия жесткие, в комментариях есть навес для велосипедов.

Пожалуйста, предложите дополнительные функции для включения в комментарии. В идеале, пожалуйста, укажите, где такая функция используется в stdlib (например, math / big), инструментах или популярных пакетах.

На данный момент предложены и рассматриваются следующие функции:

  • Повернуть влево / Повернуть вправо

    • Плюсы: & 63 больше не нужно компилировать вращение с неконстантным аргументом в одну инструкцию на x86, x86-64 ..

    • Минусы: очень короткие / простые в реализации и встроенные.

    • Используется: crypto / использует постоянный поворот, который правильно обрабатывается компилятором.

  • ReverseBits

    • Плюсы:?

    • Минусы:?

    • Использовал: ?

  • Add / Sub с возвратом переноса

    • Плюсы: В противном случае дорого

    • Минусы:?

    • Используется: математика / большой

История

14 января: уточнен вывод TrailingZeros и LeadingZeros, когда аргумент равен 0.
14. января: переименованы методы: CountTrailingZeros -> TrailingZeros, CountLeadingZeros -> LeadingZeros, CountOnes -> Ones.
13 января: фиксированное название архитектуры.
11 января: Первоначальное предложение открыто для публики.

FrozenDueToAge Proposal-Accepted

Самый полезный комментарий

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

Все 168 Комментарий

@brtzsnr, возможно, вам следует отправить этот документ в этапах процесса предложения ?

Поскольку это уже уценка по шаблону, должно быть легко скопировать и вставить в CL, создав файл design / 18616-bit-twiddling.md (или что-то еще).

@cespare с https://github.com/golang/proposal «если автор хочет написать проектный документ, он может его написать». Это началось как дизайнерская документация, и если есть сильное чувство, что я должен отправить это, я в полном порядке.

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

(Например, math / big также реализует nlz (== clz).)

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

bits.TrailingZeros64(x) вместо bits.CountTrailingZeros64(x)

и так далее.

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

(Это CL с API и базовой реализацией - для целей обсуждения вместо проектной документации. Нам все еще нужно решить, следует ли принимать это предложение или нет.)

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

Последняя арка, указанная в таблице поддержки оборудования, - это "BSWAP" - опечатка?

Спасибо, что написали это.

Строка документа для ctz и clz должна указывать результат при передаче 0.

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

Спасибо за предложение.
Я просто хочу добавить, что мы, вероятно, также хотим сосредоточиться на использовании, а не только на инструкциях. Например, @ dr2chase однажды обнаружит, что в компиляторе / ассемблере есть несколько log2 функций. Это близко к CLZ, но не то же самое. Подобные функции, вероятно, также должны быть в пакете бит-тиддлинга. И, возможно, также включить полезные функции, не относящиеся к этим инструкциям.

Как насчет того, чтобы предоставить пакет для всех примитивов тиддлинга битов, определенных
Восторг хакера?

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

@minux , к счастью, все функции, которые мне нужны до сих пор, - это именно те, которые есть в этом предложении.

Преимущество Follow Hacker's Delight в том, что нам не нужно тратить время на споры об именах.

Хочу добавить следующее:

ReverseBits (для uint32 и uint64)
RotateLeft / Right (может быть расширен за счет двух сдвигов, но компилятор
не всегда может выполнить преобразование из-за проблем с диапазоном сдвига)

Может быть, также два результата формы добавления и вычитания? Например

func AddUint32 (x, y, carryin uint32) (carryout, sum uint32) // перенос должен
быть 0 или 1.
Аналогично для uint64.

И SqrtInt.

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

Соответственно, функции для проверки переполнения при сложении / умножении.

Связанные данные: есть различные проблемы, которые были зарегистрированы для более быстрого cgo. В одном из таких примеров (предложение №16051) тот факт, что быстрая реализация bsr / ctz / etc. Было упомянуто, что мы надеемся избавиться от множества вариантов использования, когда люди, пишущие на go, испытывают соблазн использовать cgo.

@aclements прокомментировал 1 июля 2016 г .:

@eloff , было некоторое обсуждение (хотя, насколько мне известно, никаких конкретных предложений) о добавлении функций для таких вещей, как popcount и bsr, которые были бы скомпилированы как встроенные функции, если они поддерживаются (например, math.Sqrt сегодня). В версии 1.7 мы пробуем это в среде выполнения, которая теперь имеет встроенный ctz, доступный на amd64 SSA. Очевидно, это не решает общей проблемы, но устраняет еще одну причину использования cgo в контексте с низкими накладными расходами.

Многих (в том числе и меня) привлекает производительность, поэтому такие вещи, как это текущее предложение по битовому тиддлингу, могут помочь. (И да, cgo теперь работает быстрее в 1.8, что тоже хорошо).

RotateLeft / Right (может быть расширен за счет двух сдвигов, но компилятор
не всегда может выполнить преобразование из-за проблем с диапазоном сдвига)

@minux Вы можете уточнить, в чем проблема?

@brtzsnr : Я думаю, что Minux имеет в виду, что когда вы пишете (x << k) | (x >> (64-k)) , вы знаете, что используете 0 <= k < 64 , но компилятор не может читать ваши мысли, и это не очевидно выводимый из кода. Если бы у нас была функция

func leftRot(x uint64, k uint) uint64 {
   k &= 63
   return (x << k) | (x >> (64-k))
}

Затем мы можем убедиться (с помощью & 63), что компилятор знает, что диапазон k ограничен.
Поэтому, если компилятор не может доказать, что ввод ограничен, нам нужно дополнительное И. Это лучше, чем вообще не создавать вращающуюся сборку.

В пятницу, 13 января 2017 г., в 22:37, Кейт Рэндалл [email protected]
написал:

@brtzsnr https://github.com/brtzsnr : я думаю, что имеет в виду Minux
к тому, что когда вы пишете (x << k) | (x >> (64-k)), вы знаете, что используете 0
<= k <64, но компилятор не может читать ваши мысли, и это не очевидно
выводимый из кода. Если бы у нас была функция

func leftRot (x uint64, k uint) uint64 {
k & = 63
return (x << k) | (х >> (64-к))
}

Затем мы можем убедиться (с помощью & 63), что компилятор знает, что диапазон
k ограничен.
Поэтому, если компилятор не может доказать, что ввод ограничен, нам понадобится дополнительный
А ТАКЖЕ. Это лучше, чем вообще не создавать вращающуюся сборку.

Верно. Если мы определим функции RotateLeft и RotateRight, мы можем формально
определить функцию поворота левых / правых k битов (независимо от того, какое k). Это
аналогично тому, как определяются наши сменные операции. И это определение тоже
красиво отображается на фактическую инструкцию поворота (в отличие от сдвигов, где больше
интуитивное определение требует сравнения на определенных архитектурах).

Как насчет функций перестановки (и перестановки) байтов и битов, которые используются библиотекой сжатия blosc ? Слайды (тасовка начинается со слайда 17). Эти функции могут быть ускорены SSE2 / AVX2.

13 января 2017 г. в 23:24 opennota [email protected] написал:

Как насчет функций перестановки байтов и битов, которые используются в blosc?
https://github.com/Blosc/c-blosc библиотека сжатия? Слайды
http://www.slideshare.net/PyData/blosc-py-data-2014 (перетасовка
начинается со слайда 17). Эти функции могут быть ускорены SSE2 / AVX2.

SIMD - более серьезная проблема, и она выходит за рамки этого пакета. Это

17373.

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

@minux, а также все остальные: знаете ли вы, где используется поворот влево / вправо с непостоянным количеством повернутых битов? crypto / sha256 использует, например, поворот, но с постоянным количеством бит.

rotate легко написать встроенным способом, который может распознать компилятор.

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

Вы знаете, где используется поворот влево / вправо с непостоянным числом повернутых битов?

Вот пример из № 9337:

https://play.golang.org/p/rmDG7MR5F9

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

В субботу, 14 января 2017 г., в 5:05, Александр Моцой [email protected]
написал:

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

Как я много раз подчеркивал в этом выпуске, это неправильный способ
разработать пакет Go. Он слишком сильно привязан к базовому оборудованию. Что мы
want - неплохой пакет, который обычно бывает полезен. Ли
функции могут быть расширены в одну инструкцию, не имеет значения, пока
поскольку интерфейс API хорошо известен и в целом полезен.

@minux https://github.com/minux, а также все остальные: знаете ли вы
где поворот влево / вправо используется с непостоянным количеством повернутых бит?
crypto / sha256 использует, например, поворот, но с постоянным количеством бит.

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

Один простой пример использования переменного числа оборотов - интересный
реализация popcount:
// https://play.golang.org/p/ctNRXsBt0z

func RotateRight(x, k uint32) uint32
func Popcount(x uint32) int {
    var v uint32
    for i := v - v; i < 32; i++ {
        v += RotateRight(x, i)
    }
    return int(-int32(v))
}

@josharian Пример выглядит как плохое решение для инлайнера, если rot не встраивается. Вы пытались написать функцию как func rot(x, n) вместо rot = func(x, n) ?

@minux : Я с тобой согласен. Я не пытаюсь привязать API к определенному набору инструкций; аппаратная поддержка - приятный бонус. Моя основная цель - найти способы использования в реальном коде (а не в игрушечном коде), чтобы понять контекст, что является лучшей сигнатурой и насколько важно предоставить всем любимую функцию. Обещание совместимости укусит нас позже, если мы не сделаем это правильно сейчас.

Например: Какой должна быть подпись добавления с возвратом переноса? Add(x, y uint64) (c, s uint64) ? Глядя на math/big нам, вероятно, тоже понадобится Add(x, y uintptr) (c, s uintptr) .

Пример выглядит как плохое решение, если rot не встроен.

да. Это часть ошибки, связанной с встраиванием. :)

Вы пытались написать функцию как func rot (x, n) вместо rot = func (x, n)?

Это не мой код - и в этом суть. И вообще, это разумный код.

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

В четверг, 19 января 2017 г., в 11:50, Майкл Мандей [email protected]
написал:

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

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

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

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

Хотя я понимаю необходимость таких гарантий, но на самом деле это выходит за рамки
наш контроль.

Я склонен согласиться с @minux. Если вам нужны криптографические примитивы с постоянным временем, они должны жить в crypto / subtle. crypto / subtle может легко перенаправить на math / bits на платформах, где эти реализации были проверены. Они могут делать что-то еще, если требуется более медленная, но постоянная реализация.

Кажется, это стоит того. Уезжаем к @ randall77 к ветеринару. Следующий шаг выглядит как проектная документация, помещенная в обычное место (я вижу эскиз выше).

Теперь, когда это предложение может быть реализовано, мы должны согласовать детали API. Вопросы, которые я вижу на данный момент:

  • какие функции включить?
  • какие типы обрабатывать (только uint64 , uint32 или uint64 , uint32 , uint16 , uint8 или uintptr )?
  • детали подписи (например, TrailingZeroesxx(x uintxx) uint , где xx = 64, 32 и т. д., vs TrailingZeroes(x uint64, size uint) uint где размер равен 64, 32 и т. д.) - последнее приведет к меньшему API и может по-прежнему будьте достаточно быстрыми в зависимости от реализации)
  • какое-то название велосипеда (мне нравится терминология "Хакерский восторг", но ее написание может быть более уместным)
  • есть ли лучшее место для этого пакета, чем math / bits.

@brtzsnr Хотели бы вы продолжить эту работу? Я в порядке, если вы хотите взять свой первоначальный дизайн и повторить его в этом выпуске или если вы предпочитаете создать специальный документ по дизайну. Как вариант, я могу поднять это и подтолкнуть вперед. Мне нравится видеть это во время фазы 1.9.

Лично я предпочитаю прописанные имена xx: биты.TrailingZeroes (uint64 (x), 32) более громоздкий и менее читаемый, чем биты.TrailingZeroes32 (x), imo, и эта схема именования будет работать с платформой переменного размера uintptr проще (xx = Ptr?).

Это также сделало бы это более очевидным, если бы вы, скажем, не включили uint8 в первый выпуск, а добавили его позже - внезапно появилось бы много новых функций xx = 8 вместо кучи обновленных комментариев к документу, которые добавляют 8 к списку допустимых размеров с пометкой в ​​документации к выпуску, которую легко пропустить.

Если используются написанные имена, я думаю, что термины восторга хакера должны быть включены в описания как "также известные как", чтобы поисковые запросы (на странице или через поисковую систему) легко находили каноническое имя, если вы ищете неправильное написание .

Я думаю, что математика / биты - прекрасное место, это математика с битами.

Я должен отметить, что SwapBytes{16,32,64} более согласован с функциями в sync/atomic чем SwapBytes(..., bitSize uint) .

Хотя пакет strconv действительно использует шаблон ParseInt(..., bitSize int) , между bits и atomic больше взаимосвязи, чем с strconv .

Три причины, по которым мне не нравится SwapBytes (uint64, size uint):

  1. люди могут использовать его в тех случаях, когда размер не является постоянной времени компиляции (при
    по крайней мере для текущего компилятора),
  2. это требует множества преобразований типов. Сначала преобразовать uint32 в
    uint64, а затем преобразовать его обратно.
  3. мы должны зарезервировать общее имя SwapBytes для будущего общего
    версия функции (когда Go получает поддержку дженериков).

@griesemer Роберт, примите предложение. У меня будет время продвинуть это предложение в течение одного месяца, но прогресс не должен останавливаться до тех пор.

Похоже, есть явное предпочтение подписям формы:

func fffNN(x uintNN) uint

что оставляет открытым, какой NN поддерживать. Давайте сосредоточимся на NN = 64 с целью продолжения обсуждения. Будет просто добавить остальные типы по мере необходимости (включая uintptr).

Это приводит нас прямо к исходному предложению @brtzsnr и следующим функциям (и их соответствующим вариациям для разных типов), а также к тому, что люди предлагали тем временем:

// LeadingZeros64 returns the number of leading zero bits in x.
// The result is 64 if x == 0.
func LeadingZeros64(x uint64) uint

// TrailingZeros64 returns the number of trailing zero bits in x.
// The result is 64 if x == 0.
func TrailingZeros64(x uint64) uint

// Ones64 returns the number of bits set in x.
func Ones64(x uint64) uint

// RotateLeft64 returns the value of x rotated left by n%64 bits.
func RotateLeft64(x uint64, n uint) uint64

// RotateRight64 returns the value of x rotated right by n%64 bits.
func RotateRight64(x uint64, n uint) uint64

У нас также может быть набор функций Swap. Лично я скептически отношусь к этому: 1) Мне никогда не были нужны SwapBits, и большинство случаев использования SwapBytes связано с кодом, учитывающим порядок байтов, хотя этого быть не должно. Комментарии?

// SwapBits64 reverses the order of the bits in x.
func SwapBits64(x uint64) uint64

// SwapBytes64 reverses the order of the bytes in x.
func SwapBytes64(x uint64) uint64

Тогда может быть набор целочисленных операций. Они будут только в этом пакете, потому что некоторые из них (например, Log2) могут быть близки к другим функциям в этом пакете. Эти функции могут быть где угодно. Возможно, необходимо изменить имя пакета bits . Комментарии?

// Log2 returns the integer binary logarithm of x.
// The result is the integer n for which 2^n <= x < 2^(n+1).
// If x == 0, the result is -1.
func Log2(x uint64) int

// Sqrt returns the integer square root of x.
// The result is the value n such that n^2 <= x < (n+1)^2.
func Sqrt(x uint64) uint64

Наконец, @minux предлагает такие операции, как AddUint32 и т. Д. Я собираюсь их пока опустить, потому что считаю, что их сложнее правильно указать (и там больше разнообразия). Мы можем вернуться к ним позже. Давайте придем к консенсусу по вышеизложенному.

Вопросов:

  • Есть какие-нибудь мнения об именах функций?
  • Какие-нибудь мнения о целочисленных операциях?
  • Есть какие-нибудь мнения о названии / местонахождении пакета?

Я бы предпочел длинные имена функций. Go избегает сокращений в именах функций. В комментариях могут быть указаны их псевдонимы («nlz», «ntz» и т. Д.) Для людей, ищущих пакет таким образом.

@griesemer , похоже, в вашем сообщении есть опечатки. Комментарии не соответствуют сигнатурам функций.

Я использую SwapBits в приложениях для сжатия. Тем не менее, предоставляют ли основные архитектуры какие-либо возможности для эффективного реверсирования битов? Я планировал просто сделать в своем собственном коде:

v := bits.SwapBytes64(v)
v = (v&0xaaaaaaaaaaaaaaaa)>>1 | (v&0x5555555555555555)<<1
v = (v&0xcccccccccccccccc)>>2 | (v&0x3333333333333333)<<2
v = (v&0xf0f0f0f0f0f0f0f0)>>4 | (v&0x0f0f0f0f0f0f0f0f)<<4

@bradfitz , @dsnet : обновлены подписи (исправлены опечатки). Также обновленные вопросы (люди предпочитают явные названия ярлыков).

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

Зачем исключать что-то исключительно на основании наличия инструкций? Обратный бит
имеет заметное применение в БПФ.

Чтобы ответить на ваш вопрос о доступности инструкций обратного бита: arm has
инструкция RBIT.

@griesemer Что касается сигнатур типов таких функций, как

func Ones64(x uint64) uint
func RotateLeft64(x uint64, n uint) uint64

Принимает ли RotateLeftN т. Д. Uints, потому что это необходимо для легкой интринизации (когда это возможно) или просто потому, что это домен? Если нет сильной технической необходимости, есть более сильный прецедент для использования целых чисел, даже если отрицательные значения не имеют смысла. Если есть сильная техническая необходимость, должны ли они быть uintN для соответствующего N для регулярности?

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

@jimmyfrasche Потому что это домен. Процессору все равно. Является ли что-то int или uint, не имеет значения для большинства операций, кроме сравнений. Тем не менее, если мы сделаем его int, мы должны объяснить, что он никогда не бывает отрицательным (результат) или что он не должен быть отрицательным (аргумент), или указать, что он должен делать, когда он отрицательный (аргумент для вращения). В этом случае мы могли бы обойтись только одним поворотом, что было бы неплохо.

Я не уверен, что использование int упрощает задачу. При правильном проектировании uints будут переходить в uints (как в случае с math / big), и никаких преобразований не требуется. Но даже если преобразование потребуется, оно будет бесплатным с точки зрения стоимости процессора (но не с точки зрения удобочитаемости).

Но я рад услышать / увидеть убедительные аргументы в противном случае.

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

Причина отказа от использования int заключается в том, что неясно, будет ли
RotateRight -2 бита равняется RotateLeft 2 бита.

@minux Нет двусмысленности, когда Rotate(x, n) определено для поворота x на n mod 64 битов: поворот влево на 10 бит аналогичен повороту вправо на 64-10 = 54 бита, или (если мы разрешаем аргументы типа int) на -10 бит (если положительное значение означает поворот влево). -10 mod 64 = 54. Скорее всего, мы получим эффект бесплатно даже с инструкцией CPU. Например, 32-битные инструкции x86 ROL / ROR уже смотрят только на нижние 5 бит регистра CL, эффективно выполняя mod 32 для 32-битного вращения.

Единственный реальный аргумент - это регулярность с остальной частью stdlib. Есть множество мест, где uints имеют большой смысл, но вместо них используются int.

Поскольку math / big - заметное исключение, у меня нет проблем с этим, но это то, что нужно учитывать.

Я предполагаю, что @minux означал двусмысленность для кого-то, читающего / отлаживающего код, а не двусмысленность в реализации, что является убедительным аргументом в пользу использования uint.

Следует ли называть биты.SwapBits с суффиксом бит, когда имя пакета уже называется битами? Как насчет битов.

Другая идея - это биты.SwapN (v uint64, n int), а не биты.SwapBytes, где n представляет количество битов, по которым группируется своп. Когда n = 1, биты меняются местами, когда n = 8, байты меняются местами. Одно из преимуществ заключается в том, что bits.SwapBytes больше не должно существовать, хотя я никогда не видел необходимости в каком-либо другом значении n, возможно, другие не согласятся.

Как указано выше , LeadingZeros64 , TrailingZeros64 , Ones64 все LGTM.

Один Rotate64 с подписанной суммой ротации является апелляционным. Большинство вращений также будет на постоянную величину, поэтому (если / когда это станет внутренним) отсутствие отдельных функций Right / Left не будет стоить времени выполнения.

У меня нет твердого мнения о перетасовке / перестановке / реверсировании битов / байтов. Для реверсивных битов имя bits.Reverse64 кажется более подходящим. А может быть bits.ReverseBytes64 ? Я нахожу Swap64 немного непонятным. Я вижу привлекательность того, чтобы Swap64 принимали размер группы, но каково поведение, когда размер группы не делится равномерно на 64? Если имеют значение только размеры группы 1 и 8 , было бы проще дать им отдельные функции. Я также задаюсь вопросом, может ли какая-то общая манипуляция с битовым полем быть лучше, возможно, глядя на инструкции arm64 и amd64 BMI2 для вдохновения.

Я не уверен, что целочисленные математические функции входят в этот пакет. Они больше похожи на математику пакетов, где они могут использовать битовые функции в своей реализации. Связанный, почему бы не func Sqrt(x uint64) uint32 (вместо возврата uint64 )?

Хотя AddUint32 и друзья сложны, я надеюсь, что мы вернемся к ним в конце концов. Помимо прочего, MulUint64 предоставит доступ к HMUL, который даже супер-минималистичный RISC-V ISA предоставляет в качестве инструкции. Но да, давайте сначала что-нибудь сделаем; мы всегда можем расшириться позже.

bits явно похоже на правильное имя пакета. У меня есть соблазн сказать, что путь импорта должен быть просто bits , а не math/bits , но я не чувствую особого смысла.

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

ИМО, если бы это было просто bits (без чтения документации пакета), я бы ожидал, что он будет чем-то похож на пакет bytes . То есть, помимо функций для работы с битами, я ожидал бы найти Reader и Writer для чтения и записи битов в / из байтового потока. Если вы сделаете его math/bits станет более очевидным, что это просто набор функций без сохранения состояния.

(Я не предлагаю добавлять Reader / Writer для битовых потоков)

Один Rotate64 с подписанной величиной поворота выглядит привлекательно. Большинство вращений также будет на постоянную величину, поэтому (если / когда это станет внутренним) отсутствие отдельных функций Right / Left не будет стоить времени выполнения.

Я вижу, как было бы удобно иметь немного меньший API пакета, объединив вращение влево / вправо в один Rotate , но тогда есть также вопрос эффективного документирования параметра n для указывают, что:

  • отрицательное значение n приводит к повороту влево
  • ноль n приводит к изменениям
  • положительное значение n приводит к повороту вправо

Мне кажется, что дополнительные умственные затраты и накладные расходы на документацию не оправдывают объединение этих двух вещей в один Rotate . Наличие явных RotateLeft и RotateRight где n - это uint кажется мне более интуитивным.

@mdlayher Имейте в виду, что поворот n-битного слова на k бит влево аналогичен повороту nk битов вправо. Даже если вы разделите это на две функции, вам все равно нужно понимать, что вращение на k бит влево всегда означает вращение на (k mod n) бит (если вы вращаете более чем на n бит, вы начинаете заново). Как только вы это сделаете, вы можете просто сказать, что функция поворота всегда вращается на k mod n бит, и все готово. Нет необходимости объяснять отрицательное значение или вторую функцию. Просто получается. На самом деле все проще.

Вдобавок ко всему, оборудование (например, на x86) даже делает это за вас автоматически, даже для непостоянного k.

@griesemer , обратная сторона Rotate заключается в том, что он не говорит, какое направление является положительным. Rotate32(1, 1) равно 2 или 0x80000000? Например, если бы я читал код, который использовал это, я бы ожидал, что результат будет 2, но, очевидно, @mdlayher ожидал бы, что это будет 0x80000000. С другой стороны, RotateLeft и RotateRight - это однозначные имена, независимо от того, принимают ли они аргумент со знаком или без знака. (Я не согласен с @minux, что неоднозначно, равно ли RotateRight на -2 RotateLeft 2. Мне они кажутся явно эквивалентными, и я не понимаю, как еще вы могли бы их указать.)

@aclements, которые можно исправить, имея только одну функцию с именем RotateLeft64 и используя отрицательные значения для поворота вправо. Или с документами. (FWIW, в вашем примере я также ожидал бы 2, а не 0x800000000.)

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

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

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

Я убежден.

@aclements @ianlancetaylor Очко взято. (Тем не менее, реализация RotateLeft / Right, вероятно, просто вызовет одну реализацию поворота.)

Идея имени состоит в том, чтобы указать тип в имени пакета, а не в имени подписи. bits64.Ones вместо bits.Ones64 .

@btracey , меня flag64.Int или math64.Floatfrombits или sort64.Floats или atomic64.StoreInt .

@bradfitz golang.org/x/image/math/f64 и golang.org/x/image/math/f32 существуют, хотя

Я не думал о atomic как о прецеденте (к счастью, не в моем обычном использовании Go). Другие примеры отличаются, потому что пакеты больше, чем набор функций, повторяющихся для ряда различных типов.

Документацию легче смотреть (скажем, на godoc), когда вы переходите к bits64 и видите
Ones LeadingZeros TrailingZeros

Вместо того, чтобы перейти к bits и посмотреть
Ones8 Ones16 Ones32 Ones64 LeadingZeros8 ...

@iand , это тоже довольно

В Go есть довольно веский прецедент использования стиля именования pkg.foo64, а не pkg64.foo.

API пакета действительно становится в n раз больше, если у нас есть n типов, но сложность API - нет. Лучшим подходом к решению этой проблемы может быть документация и представление API с помощью такого инструмента, как go doc. Например, вместо:

// TrailingZeros16 returns the number of trailing zero bits in x.
// The result is 16 if x == 0.
func TrailingZeros16(x uint16) uint

// TrailingZeros32 returns the number of trailing zero bits in x.
// The result is 32 if x == 0.
func TrailingZeros32(x uint32) uint

// TrailingZeros64 returns the number of trailing zero bits in x.
// The result is 64 if x == 0.
func TrailingZeros64(x uint64) uint

что явно увеличивает умственные затраты при просмотре API, мы могли бы представить это как:

// TrailingZerosN returns the number of trailing zero bits in a uintN value x for N = 16, 32, 64.
// The result is N if x == 0.
func TrailingZeros16(x uint16) uint
func TrailingZeros32(x uint32) uint
func TrailingZeros64(x uint64) uint

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

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

+1 при указании размера в битах в имени пакета.

bits64.Log2 читается лучше, чем bits.Log264 (я чувствую, что Log2 принадлежит ему, а именование пакетов не является хорошей причиной для его хранения)

В конце концов, это один и тот же API для каждого типа, "параметризованного" скалярным типом, поэтому, если функции имеют одно и то же имя для разных типов, код легче реорганизовать с помощью отдельных пакетов - просто измените путь импорта (замена целого слова тривиально с gofmt -r, но пользовательские преобразования суффиксов более неудобны).

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

Мы могли бы украсть игру из кодирования / двоичного кода и создать переменные с именами Uint64, Uint32, ..., которые будут иметь методы, принимающие связанные предопределенные типы.

bits.Uint64.RightShift
bits.Uint8.Reverse
bits.Uintptr.Log2
...

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

@rogpeppe это могут быть биты. Log64 и документы говорят, что это была база 2 (что еще было бы в пакете битов?) Хотя так же беспорядочно, как наличие пакетов 8/16/32/64 / ptr на одном уровне, это сделать их каждого уборщика индивидуально. (Также похоже, что ваша передача была прервана на середине предложения.)

@nerdatmath это будет немного иначе, поскольку у них будет один и тот же интерфейс в разговорном смысле, но не в смысле Go, как в случае с кодированием / двоичным (они оба реализуют binary.ByteOrder), поэтому переменные будут просто для пространства имен и godoc не выберет ни один из методов (# 7823), если типы не были экспортированы, что по-другому беспорядочно.

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

@nerdatmath, если они вары, то они изменяемы (можно сделать bits.Uint64 = bits.Uint8 ), что не позволит компилятору рассматривать их как внутренние, что было одной из мотиваций (по крайней мере, для части) пакета.

Переменные на самом деле не изменяемы, если они неэкспортированного типа и не имеют состояния (неэкспортированная пустая структура). Но без общего интерфейса годок (сегодня) был бы ужасен. Хотя это поправимо, если это ответ.

Если вы все же в конечном итоге собираетесь использовать несколько пакетов, если есть достаточное количество повторений и объем, чтобы гарантировать это, по крайней мере, возможно, назовите пакеты как «X / bits / int64s», «X / bits / ints», «X / биты / int32s "для соответствия" ошибкам "," строкам "," байтам ". По-прежнему кажется, что много пакетов взрывается.

Я не думаю, что мы должны идти по пути к нескольким пакетам, зависящим от типа, иметь один бит пакета просто и не увеличивает количество пакетов в библиотеке Go.

Я не думаю, что Ones64 и TrailingZeroes64 - хорошие имена. Они не сообщают, что возвращают счет. Добавление префикса Count делает имена еще длиннее. В моих программах эти функции часто встраиваются в более крупные выражения, поэтому короткие имена функций улучшают читаемость. Хотя я использовал имена из книги «Hacker's Delight», я предлагаю следовать мнемонике ассемблера Intel: Popcnt64, Tzcnt64 и Lzcnt64. Имена короткие, следуют последовательному шаблону, информируют о том, что счетчик возвращен и уже установлен.

Обычно счетчики в Go возвращаются как целые числа, даже если счетчик никогда не может быть отрицательным. Ярким примером является встроенная функция len, но все функции Read, Write, Printf и т. Д. Также возвращают целые числа. Я считаю довольно неожиданным использование значения uint для подсчета и предлагаю вернуть значение int.

Если выбор стоит между (для злоупотребления обозначениями) math / bits / int64s.Ones и math / bits.Ones64, то я определенно предпочел бы один пакет. Более важно иметь биты в идентификаторе пакета, чем разбивать его по размеру. Наличие битов в идентификаторе пакета облегчает чтение кода, что более важно, чем незначительное неудобство повторения в документации пакета.

@ulikunitz Вы всегда можете сделать ctz := bits.TrailingZeroes64 и т.п. в коде, который широко использует биты. Это обеспечивает баланс между глобально самодокументированными именами и локально компактными именами для сложного кода. Противоположное преобразование, countTrailingZeroes := bits.Ctz64 , менее полезно, поскольку хорошие глобальные свойства уже потеряны.

Я очень редко балуюсь с вещами на битовом уровне. Мне приходится каждый раз искать много такого материала просто потому, что я много лет не думал об этом, поэтому я нахожу все имена в стиле Hacker's Delight / asm чрезвычайно загадочными. Я бы предпочел, чтобы он просто сказал, что он сделал, чтобы я мог определить тот, который мне нужен, и вернуться к программированию.

Я согласен с @ulikunitz, длинные имена вызывают

init() instead of initialize(), 
func instead of function
os instead of operatingsystem
proc instead of process
chan instead of channel

Кроме того, я бы бросил вызов этим битам. TrailingZeroes64 самодокументируется. Что означает завершение нуля? Свойство самостоятельной документации существует с предположением, что пользователь с самого начала что-то знает о семантике документации. Если не использовать Hacker's Delight для единообразия, почему бы просто не назвать его ZeroesRight64 и ZeroesLeft64, чтобы соответствовать RotateRight64 и RotateLeft64?

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

Например, если я вызываю строки. в конечном итоге назову это связкой, поэтому я сделаю ends := strings.HasSuffix что короче и сделает код более понятным. Это нормально, потому что определение псевдонима под рукой.

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

TrailingZeroes, возможно, не сможет точно сказать вам, что происходит, если вы не знакомы с концепцией, но он дает вам гораздо лучшее представление о том, что происходит, по крайней мере приблизительно, чем Ctz.

@jimmyfrasche Что касается https://github.com/golang/go/issues/18616#issuecomment -275828661: go / doc может легко распознать непрерывную последовательность функций в одном файле, имена которых начинаются с тот же префикс и суффикс, который представляет собой просто последовательность чисел и / или, возможно, слово, которое является «коротким» относительно префикса. Я бы совмещал это с тем фактом, что только первая из этих функций имеет строку документа, а другая с совпадающим префиксом - нет. Я думаю, что это могло бы хорошо сработать в более общих настройках. Тем не менее, давайте обсудим это в другом месте, чтобы не перехватить это предложение.

К вашему сведению, создан номер 18858, чтобы обсудить изменения go/doc и сохранить этот разговор отдельно от этого.

@mdlayher спасибо за это.

@rogpeppe Указание размера в имени пакета звучит интригующе, но, судя по комментариям и с учетом существующего стиля Go, я думаю, что предпочтительнее

@ulikunitz Обычно я одним из первых голосую за короткие имена; особенно в локальном контексте (и в глобальном контексте для чрезвычайно часто используемых имен). Но здесь у нас есть пакетный API, и (по крайней мере, по моему опыту) функции не будут широко отображаться в клиентах. Например, math / big make использует LeadingZeros, Log2 и т. Д., Но это всего лишь один или два вызова. Я думаю, что более длинное описательное имя - это нормально, и, судя по обсуждению, большинство комментариев в пользу этого. Если вы часто вызываете функцию, обернуть ее в функцию с коротким именем дешево. Дополнительный вызов будет встроен. FWIW, мнемоника Intel тоже не очень хороша, их акцент делается на «count» (cnt), а затем у вас остаются 2 буквы, которые необходимо расшифровать.

Я согласен, что 2 в Log2 не нужны. bits.Log означает основание 2.

биты. Log64 SGTM.

@griesemer Мой

@griesemer Интересно, стоит ли и дальше

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

@ulikunitz , дополнительно:

Большой пакет внутренне использует nlz, а не LeadingZeros.

Внутренние частные неэкспортированные имена здесь не влияют.

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

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

CL https://golang.org/cl/36315 упоминает об этой проблеме.

Я загрузил https://go-review.googlesource.com/#/c/36315/ в качестве исходного (частично протестированного) API (с предварительной реализацией) для проверки (вместо проектного документа).

Мы все еще следим за правильностью API, поэтому пока просьба комментировать только API (bits.go). В случае незначительных проблем (опечаток и т. Д.) Просьба комментировать CL. По вопросам дизайна, пожалуйста, прокомментируйте эту проблему. Я буду обновлять CL, пока мы не будем довольны интерфейсом.

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

@griesemer Я уверен, что вы в порядке, но если это полезно, у меня есть реализация сборки CLZ с тестами https://github.com/ericlagergren/decimal/tree/ba42df4f517084ca27f8017acfaeb69629a090fb/internal/arith, что вы можете украсть / возиться с / чем угодно.

@ericlagergren Спасибо за ссылку. После того, как API устоялся и у нас есть тесты повсюду, мы можем приступить к оптимизации реализации. Я буду иметь это в виду. Спасибо.

@griesemer , похоже, ваша предлагаемая документация по API зависит от того, сможет ли go / doc адекватно документировать функции с тем же префиксом, но с целочисленным суффиксом.

Планируется ли работа над этим и до версии 1.9?

18858, то есть!

@mdlayher Это было бы идеально. Но это не было бы препятствием, поскольку он затрагивает «только» документацию, а не API (который должен оставаться обратно совместимым в будущем).

@griesemer @bradfitz Я потратил некоторое время, чтобы переосмыслить свою позицию относительно имен функций. Важной целью выбора имен должна быть читаемость кода с использованием API.

Следующий код действительно легко понять:

n := bits.LeadingZeros64(x)

Я все еще немного сомневаюсь в ясности Ones64 (x), но она согласуется с LeadingZeros и TrailingZeros. Так что я остаюсь в своей позиции и поддерживаю текущее предложение. В качестве эксперимента я использовал свой существующий код для экспериментальной чистой реализации пакета на Go, включая тестовые примеры.

Репозиторий: https://github.com/ulikunitz/bits
Документация: https://godoc.org/github.com/ulikunitz/bits

У нас также может быть набор функций Swap, [...] большинство использования SwapBytes связано с кодом, который учитывает порядок байтов, хотя этого быть не должно. Комментарии?

По этой причине я бы хотел, чтобы SwapBytes не использовались в API. Я беспокоюсь, что люди начнут использовать его непереносимыми способами, потому что он проще (или считается быстрее), чем binary/encoding.BigEndian .

По этой причине я бы хотел, чтобы SwapBytes не использовался в API. Я беспокоюсь, что люди начнут использовать его непереносимыми способами, потому что он проще (или считается быстрее), чем двоичный / кодирующий. BigEndian.

@mundaym будут ли эти процедуры когда-нибудь SwapBytes64 компилировать напрямую как BSWAP может перевесить людей, использующих его неправильно, imo.

Я беспокоюсь, что люди начнут использовать его непереносимыми способами, потому что он проще (или считается быстрее), чем двоичный код / ​​кодирование.

Я считаю, что использование ReverseBytes непереносимо только в сочетании с unsafe когда вы получаете доступ к данным на низком уровне в том виде, как машина размещает их в памяти. Однако использование encoding.{Little,Big}Endian.X так же непереносимо при таком использовании. Я с нетерпением жду ReverseBytes для использования в сжатии и было бы грустно, если бы он не был включен.

@ericlagergren

Будут ли когда-нибудь встроены эти процедуры?

Да, функции в encoding/binary которые выполняют перестановку байтов, используют шаблоны кода, которые распознаются компилятором и (или могут быть) оптимизированы для отдельных инструкций (например, BSWAP ) на платформах, поддерживающих невыровненные данные. доступы (например, 386, amd64, s390x, ppc64le и, возможно, другие).

Разрешение SwapBytes64 компилироваться напрямую, поскольку BSWAP может перевесить людей, использующих его неправильно, imo.

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

Обратите внимание, что среда выполнения / internal / sys уже оптимизировала встроенные версии Go, сборки и компилятора для конечных нулей и обмена байтами, поэтому мы можем повторно использовать эти реализации.

@dsnet Интересно, а вы имеете в виду что-то конкретное?

@aclements Знаете ли вы, где встроенная

@mundaym ,

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

Об этом примечании: нам, вероятно, следует включить версии для uintptr поскольку не гарантируется (хотя вероятно), что он того же размера, что и uint32 или uint64 . (Обратите внимание, что uint всегда либо uint32 или uint64 ). Мнения? Оставить на потом? Какие бы имена были хорошими? ( LeadingZerosPtr ?).

Мы должны сделать uintptr сейчас, иначе math / big не сможет его использовать. Суффикс Ptr SGTM. Я думаю, мы также должны сделать uint , иначе весь вызывающий код должен написать условный код около размера int, чтобы сделать вызов без преобразования. Никаких идей по именованию.

Ptr для uintptr, без суффикса для uint. Ones, Ones8, Ones16, Ones32, Ones64, OnesPtr и т. Д.

Возможно, меня здесь меньшинство, но я против добавления функций, которые принимают uintptr.
math / big с самого начала должен был использовать uint32 / uint64. (На самом деле, я бы
просто используйте uint64 для всех архитектур и оставьте оптимизацию
компилятор.)

Проблема вот в чем:
math / big хочет использовать собственный размер слова для оптимальной производительности,
однако, в то время как uintptr не является правильным выбором.
нет гарантии, что размер указателя такой же, как у
родной размер слова.
Ярким примером является amd64p32, и мы также можем добавить mips64p32 и
mips64p32le позже, которые гораздо более популярны для 64-битных хостов MIPS.

Я определенно не хочу поощрять такое использование. uintptr самый
для указателей, а не для целых чисел, нейтральных с точки зрения архитектуры.

Однако одним из решений является введение псевдонима типа в API (возможно, это
должен быть фирменным шрифтом, это обсуждается).
введите Word = uint32 // или uint64 на 64-битных архитектурах
// нам, вероятно, также нужно предоставить несколько идеальных констант для Word, например
количество бит, маска и т. д.

А затем введите функции без префикса типа, чтобы взять Word.

На самом деле, я полагаю, что если math / bit предоставляет два результата add, sub, mul,
div; math / big можно переписать без какой-либо архитектурно-зависимой сборки.

математика / большой экспорт type Word uintptr . Я согласен с тем, что это могло быть ошибкой, но я считаю, что совместимость требует, чтобы это оставалось. Итак, если мы хотим использовать math / bits для реализации math / big, что, я думаю, мы и делаем, нам нужны API uintptr.

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

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

FWIW, реализация math / big все еще может использовать API битов на основе uint32 / 64 (если не было версии функций uintptr).

@minux Я не против двух результатов add, sub, mul, div, но давайте сделаем это на втором этапе. Однако нам все еще нужен ассемблерный код, если мы хотим приличной производительности. Но я счастлив, что компилятор убедился в обратном ...

Я добавил в API версии uint и uintptr. Посмотрите CL и прокомментируйте. Однако я не очень доволен увеличением количества функций.

@josharian Собственный uint может иметь 32-битное значение даже на 64-битной машине. Нет никаких гарантий. Я понимаю, что uintptr также не гарантирует размер машинного слова; но в то время это казалось более разумным выбором, если оглянуться назад. Возможно, нам действительно нужен шрифт Word.

Если единственная законная потребность в функциях uintptr - это поддержка math / big, возможно, реализация math / bits могла бы быть во внутреннем пакете: используйте только материал uintptr в math / big и покажите остальное.

@jimmyfrasche , @griesemer, как это повлияет на big.Int.Bits? т.е. будет ли он по-прежнему нулевым?

uint тоже ошибочен. он 32-битный на amd64p32.
На самом деле, math / big мог использовать uint вместо uintptr, но на Go 1
int / uint всегда 32-битные, что делает uintptr единственно возможным решением.
по математике / big.Word.

Мы разрабатываем новый пакет, поэтому я не верю, что совместимость имеет большое значение.
беспокойство. math / big использует неправильный тип, но это скорее исторический
авария. Не будем углубляться в ошибку.

Я не понимаю, зачем использовать единый шрифт "Word", который всегда
эффективный целочисленный тип для данной архитектуры сложнее, чем
используя uintptr. Для пользовательского кода вы можете рассматривать его как магический тип, который
либо 32-битный, либо 64-битный, в зависимости от архитектуры. Если ты можешь написать
ваш код с использованием uintptr (ширина которого также зависит от архитектуры) в
Независимо от архитектуры, то я считаю, что вы можете сделать то же самое с Word.
И Word верен на всех архитектурах.

Для распространения API я предлагаю не использовать функции для 8
и 16-битные типы в первом проходе. Они редко используются и большинство
архитектура в любом случае будет предоставлять только 32-битные и 64-битные инструкции.

math / big определяет и экспортирует Word, но он а) напрямую не совместим с uintptr (это другой тип), и б) код, который правильно использует Word и не делает предположений о его реализациях, сможет работать с любой реализацией Word. . Мы можем это изменить. Я не понимаю, почему это должно влиять на гарантию совместимости API (хотя не пробовал). В любом случае, оставим это в стороне. Мы справимся с этим отдельно.

Можем ли мы просто сделать все типы uint с явно заданным размером? Если мы знаем размер uint / uintptr как константу, мы можем тривиально написать оператор if, который вызывает функцию нужного размера. Этот оператор if будет свернут на константу, а соответствующая функция-оболочка просто вызовет функцию размера, которая будет встроена.

Наконец, какое правильное решение относительно типа Word (независимо от math / big)? Конечно, мы могли бы определить это в математике / битах, но подходящее ли это место? Это тип, зависящий от платформы. Должно ли это быть слово заранее объявленного типа? И почему бы нет?

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

Если должны быть функции для естественного типа машинного слова, я думаю, что теперь они могут ссылаться на int / uint. Мы не использовали uint в math / big, потому что он был 32-битным в 64-битных системах, но с тех пор мы это исправили. И хотя math / big представляет собой единый мир сам по себе, имеет смысл иметь свой собственный тип big. Словом, этот пакет больше представляет собой набор вспомогательных подпрограмм выбора и выбора. Определение нового типа в этом контексте менее убедительно.

Я не знаю, нужны ли нам вообще варианты int / uint. Если они обычно необходимы, определение их в этом пакете имеет больше смысла, чем принуждение всех вызывающих к написанию операторов if. Но я не знаю, будут ли они вообще нужны.

Чтобы устранить потенциальное несоответствие с math / big, есть как минимум два решения, которые не включают здесь упоминание uintptr в API.

  1. Определите файлы word32.go и word64.go в math / big с тегами сборки и оболочками, принимающими uintptr, которые перенаправляют соответствующим образом (и убедитесь, что встраивание компилятора делает правильные вещи).

  2. Измените определение big.Word на uint. Точное определение - это внутренняя деталь реализации, даже если она представлена ​​в API. Его изменение не может сломать какой-либо код, кроме amd64p32, и даже там маловероятно, что это будет иметь значение вне math / big.

@rsc : «Мы не использовали uint в math / big, потому что он был 32-битным в 64-битных системах, но с тех пор мы это исправили». Ах, да, я полностью забыл о причине выбора uintptr. Вся суть типов Go uint / int заключалась в том, чтобы иметь целочисленные типы, отражающие естественный размер регистра машины. Я попробую изменить big.Word на uint и посмотреть, что произойдет.

Все: обновлен https://go-review.googlesource.com/#/c/36315/, чтобы исключить версии функции uintptr согласно приведенному выше обсуждению.

Я предварительно обновил math / big, чтобы использовать math / bits, чтобы увидеть эффект (https://go-review.googlesource.com/36328).

Несколько комментариев:
1) Я склоняюсь к получению результатов Leading / TrailingZeros functions uint а не int . Эти результаты обычно используются для соответствующего сдвига значения; и math/big свидетельствует об этом. Я также за то, чтобы Ones возвращал uint для единообразия. Контраргументы?

2) Я не поклонник имени One , однако согласуется с Leading / TrailingZeros . Как насчет Population ?

3) Некоторые люди жаловались, что Log не помещается в этот пакет. Log эквивалентно индексу msb (с -1 для x == 0). Так что мы могли бы назвать это MsbIndex (хотя Log кажется лучше). В качестве альтернативы мы могли бы иметь функцию Len которая является длиной x в битах; т.е. `Log (x) == Len (x) -1).

Комментарии?

Я не поклонник названия One, хотя оно согласуется с Leading / TrailingZeros. Как насчет населения?

Почему не традиционный Popcount ?

В качестве альтернативы мы могли бы иметь функцию Len, которая представляет собой длину x в битах; т.е. `Log (x) == Len (x) -1).

Len или Length звучит как хорошее имя и хорошая идея.

1.

Я не поклонник названия One, но согласуется с Leading /
TrailingZeros. Как насчет населения?

биты.

@aclements Что

Население может быть лучшим названием по историческим причинам, но на самом деле оно не говорит больше, чем Единицы, если вы не знакомы с этим термином и сталкиваетесь с той же проблемой, просто Подсчет: Население чего?

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

@aclements Что

В пакете под названием «биты» я не уверен, что еще можно считать, кроме установленных битов, но CountOnes тоже хорош и, очевидно, более явный. Добавление «единиц» также соответствует «нулям» в LeadingZeros / TrailingZeros .

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

Может быть, что-то вроде AllOnes или TotalOnes, чтобы отразить Trailing / LeadingZeroes, но указать, что, в отличие от них, позиция не принимается во внимание.

AllOnes звучит так, как будто он возвращает все единицы (может быть, в битовой маске?) Или, может быть, слово, которое состоит из всех единиц.

CountOnes и TotalOnes кажутся примерно одинаковыми, но поскольку наиболее известное название этой операции - «подсчет населения», CountOnes кажется предпочтительным.

Я загрузил новую версию (https://go-review.googlesource.com/#/c/36315/) с парой изменений:

1) Я переименовал Ones в PopCount : большинство людей не слишком обрадовались последовательному, но несколько унылому названию Ones . Каждый, кто собирается использовать этот пакет, точно знает, что делает PopCount : это имя, под которым эта функция широко известна. Это немного не согласуется с остальными, но его значение стало намного яснее. Я иду с Ральфом Уолдо Эмерсоном по этому поводу («Глупая последовательность - хобгоблин маленьких умов ...»).

2) Я переименовал Log в Len как в bits.Len . Битовая длина числа x - это количество бит, которое требуется для представления x в двоичном представлении (https://en.wikipedia.org/wiki/Bit-length). Кажется уместным и устраняет необходимость иметь здесь Log что имеет не "битовое" качество. Я считаю, что это тоже победа, потому что Len(x) == Log(x) + 1 учетом того, как мы определили Log . Кроме того, он имеет то преимущество, что теперь результат всегда> = 0, и он устраняет некоторые исправления +/- 1 в (тривиальной) реализации.

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

Комментарии?

Мой опыт работы с math / big был разочарованием из-за того, что функции заставляли меня переключаться в режим uint, когда я не переключаюсь. В math / big / prime.go я написал

for i := int(s.bitLen()); i >= 0; i-- {

хотя s.bitLen () возвращает int, а не uint, потому что я не был уверен, не глядя, и был ли я также не уверен, что какой-то будущий CL может не изменить его, чтобы вернуть uint, тем самым превратив цикл for в бесконечный цикл. Необходимость защищаться предполагает наличие проблемы.

Uints гораздо более подвержены ошибкам, чем int, поэтому мы не рекомендуем их использовать в большинстве API. Я думаю, было бы намного лучше, если бы каждая функция в math / bits возвращала int и оставила преобразование в uint, которое должно выполняться в выражении сдвига, как это и так необходимо в большинстве выражений сдвига.

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

Я поддерживаю возвращение int после greping моего кода для вызовов конечных нулей и функций popcount. Большинство вызовов - это короткие объявления переменных и сравнения типа int. Для посменных вызовов, конечно, требуется тип uint, но они на удивление редки.

Загружены мелкие твики. За +1 и комментарии оставим результаты подсчета как int .

https://go-review.googlesource.com/36315

Я оставил количество входных данных для RotateLeft/Right как uint иначе нам нужно будет указать, что происходит, когда значение отрицательное, или запретить его.

Наконец, мы можем даже оставить Len поскольку LenN(x) == N - LeadingZerosN(x) . Мнения?

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

На следующем шаге мы, возможно, захотим обсудить, нужно ли включать и какие другие функции, в частности, Add / Sub / и т. Д., Которые потребляют 2 аргумента и переносят, и производят результат и перенос .

@gri мысли о базе 10 Len function? Это было бы просто ((N - clz(x) + 1) * 1233) >> 12

Это менее «хитроумно», чем base 2, но все же полезно.
В пятницу, 10 февраля 2017 г., в 17:03 Роберт Гриземер [email protected]
написал:

Загружены мелкие твики. За +1 и комментарии оставим результаты
считается как int.

https://go-review.googlesource.com/36315

Я оставил счетчики ввода для RotateLeft / Right как uint, иначе мы будем
необходимо указать, что происходит, когда значение отрицательное, или запретить это.

Наконец, мы могли бы даже оставить Len, поскольку LenN (x) == N -
ВедущиеНолиN (x). Мнения?

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

На следующем этапе мы, возможно, захотим обсудить, можем ли мы
хотите включить, в частности, Add / Sub / и т. д., которые потребляют 2 аргумента и
нести, и производить результат, и носить.

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/golang/go/issues/18616#issuecomment-279107013 или отключить звук
нить
https://github.com/notifications/unsubscribe-auth/AFnwZ_QMhMtBZ_mzQAb7XZDucXrpliSYks5rbQjCgaJpZM4Lg5zU
.

Также. Простите меня, если я выхожу за рамки этой проблемы, но я бы предпочел проверенную арифметику. Например, AddN(x, y T) (sum T, overflow bool)

Пт, 10 февраля 2017 г., в 20:16, Эрик Лагергрен [email protected]
написал:

Также. Простите меня, если я выхожу за рамки этой проблемы, но я буду в
пользу проверенной арифметики. Например, AddN (x, y T) (сумма T, переполнение bool)

Вы говорите о подписанном переполнении? или беззнаковое переполнение (перенос / заимствование)?

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

@ericlagergren Функция (base 2) Len , хотя и почти log2, по сути, по сути является функцией подсчета битов. Функция base 10 Len самом деле является функцией журнала. Нельзя отрицать, что это полезно, но для этого пакета он кажется менее подходящим.

Да, я думаю, было бы неплохо checked арифметику

Что касается функций Add, Sub: я согласен с @minux, что перенос должен быть того же типа, что и аргумент. Кроме того, я не уверен, что нам нужно что-то еще, кроме аргументов uint для этих функций: дело в том, что (в большинстве случаев) uint имеет естественный размер регистра платформы, и это уровень, на котором эти операции имеют наибольший смысл.

Пакет с математикой / проверкой может быть лучшим домом для них. checked.Add понятнее, чем bits.Add .

@minux Я думал, что подписанная проверенная арифметика, так что переполнение.

@griesemer re: base 10 Len : имеет смысл!

Если хотите, я могу отправить CL для проверенной арифметики. Мне нравится идея @jimmyfrasche о том, чтобы это было под отдельным именем пакета.

Add / sub / mul может принадлежать или не входить в bits , но проверенная математика - не единственное использование для этих операций. В более общем плане я бы сказал, что это «широкие» арифметические операции. Для add / sub существует небольшая разница между одним битом результата переноса / переполнения и логическим индикатором переполнения, но мы, вероятно, также захотим предоставить add-with-carry и вычесть-с-заимствованием, чтобы сделать их цепочкой. А для широкого множителя в дополнительном результате гораздо больше информации, чем в переполнении.

Хорошая идея - запомнить проверенные / широкие арифметические операции, но оставим их на второй раунд.

«Каждый, кто собирается использовать этот пакет, точно знает, что делает PopCount»

Я использовал эту функциональность раньше, и почему-то я не был знаком с именем PopCount (хотя должен был, потому что уверен, что убрал реализацию с Hacker's Delight, в которой используется «pop»).

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

Относится к проверенной / широкой арифметике: # 6815. Но да, давайте обойдемся!

@griesemer написал:

Функция Len (с основанием 2), хотя и почти log2, по сути, по сути является функцией подсчета битов. Функция Len с основанием 10 на самом деле является функцией журнала. Нельзя отрицать, что это полезно, но для этого пакета он кажется менее подходящим.

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

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

@rogpeppe : PopCount на OnesCount поскольку он также получил 4 больших пальца вверх (например, мое предложение использовать PopCount ). Обращается к «количеству населения» в строке документа.

Per @rsc , оставляя на данный момент проверенные / широкие арифметические операции.

Также для @rsc все функции подсчета возвращают значения int , и для простоты использования (и с учетом # 19113) используйте значения int для подсчета вращения.

Осталось LenN функций, хотя они просто N - LeadingZerosN . Но аналогичная симметрия существует для RotateLeft / Right и у нас есть и то, и другое.

Добавлена ​​немного более быстрая реализация для TrailingZeroes и завершенные тесты.

На данный момент я считаю, что у нас есть первая полезная реализация. Просмотрите код на https://go-review.googlesource.com/36315 , особенно API. Я бы хотел, чтобы это отправили, если мы все будем счастливы.

Следующие шаги:

  • более быстрая реализация (первичная)
  • добавление дополнительных функций (второстепенное)

Мы разрабатываем новую упаковку

@minux Вы имеете в виду новую математику / большой? Можно ли где-нибудь проследить за процессом?

@TuomLarsen : @minux относится к математике / битам с "новым пакетом". Он упомянул math / big как случай, когда будет использоваться math / bit. (В будущем, пожалуйста, будьте более конкретными в своих комментариях, чтобы нам не приходилось искать и угадывать, о чем вы имеете в виду - спасибо).

CL https://golang.org/cl/37140 упоминает об этой проблеме.

Будет ли реализована интеграция с помощью компилятора в math / bits для Go 1.9?

@cespare Зависит от того, доберемся ли мы до него (@khr?). Независимо от этого, мы хотим иметь достойную платформенно-независимую реализацию. (Одна из причин, по которой мы не хотим полностью переносить math / big на использование math / bits, заключается в том, что в настоящее время у нас есть некоторая сборка для конкретной платформы в math / big, которая работает быстрее.)

На моей тарелке, по крайней мере, для арок мы уже делаем интринсики для
(386, amd64, arm, arm64, s390x, mips, возможно ppc64).

Пт, 17 февраля 2017 г., в 12:54, Роберт Гриземер < [email protected]

написал:

@cespare https://github.com/cespare Зависит от того, доберемся ли мы до него (
@khr https://github.com/khr ?). Независимо от этого, мы хотим иметь
достойная платформо-независимая реализация. (Одна из причин, по которой мы не
хотите полностью перейти от math / big к использованию math / bits, это то, что в настоящее время мы
есть некоторая сборка для конкретной платформы в math / big, которая быстрее.)

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/golang/go/issues/18616#issuecomment-280763679 или отключить звук
нить
https://github.com/notifications/unsubscribe-auth/AGkgIIb8v1X5Cr-ljDgf8tQtT4Dg2MGiks5rdgkegaJpZM4Lg5zU
.

Эта статья о самом быстром способе реализации подсчета населения на x86-64 может быть полезной: ручная сборка превосходит внутренние по скорости и простоте - Дэн Луу, октябрь 2014 г.

CL https://golang.org/cl/38155 упоминает об этой проблеме.

CL https://golang.org/cl/38166 упоминает об этой проблеме.

CL https://golang.org/cl/38311 упоминает об этой проблеме.

CL https://golang.org/cl/38320 упоминает об этой проблеме.

CL https://golang.org/cl/38323 упоминает об этой проблеме.

Еще несколько обсуждений:

Мне:

math / bits.RotateLeft в настоящее время определен для паники, если его аргумент меньше нуля.
Я хотел бы изменить это, чтобы определить RotateLeft для поворота вправо, если его arg меньше нуля.

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

Я думаю, нам следует сохранить RotateLeft и RotateRight как отдельные функции, даже если теперь одна может быть реализована с другой. Стандартное использование по-прежнему будет с неотрицательными аргументами.

Если мы собираемся что-то здесь делать, мы должны делать это замораживанием. Когда выйдет версия 1.9, мы не сможем изменить свое мнение.

Роб:

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

Мне:

Проблема в
биты.Rotate (x, -5)

При чтении этого кода неясно, поворачиваем ли мы влево или вправо.
биты.RotateRight (5)
намного понятнее, даже если это означает, что в math / bits вдвое больше функций Rotate *.

Майкл Джонс:

Знак поворота означает скачки в коде, да? Жаль.

Мне:

Нет, реализация на самом деле проще с предложенной семантикой. Замаскируйте младшие 6 бит (для 64-битного вращения), и это просто работает.

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

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

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

@bcmills @robpike см. также предыдущее обсуждение этой конкретной темы, начиная с https://github.com/golang/go/issues/18616#issuecomment -275598092 и продолжая несколько комментариев.

@josharian Я видел комментарии и все еще предпочитаю свою версию. От этого можно отказаться навсегда, но я пытаюсь сделать так, чтобы было просто реализовать, просто определить, просто понять и просто документировать. Я считаю, что единственная функция поворота с аргументом со знаком удовлетворяет всем этим требованиям, за исключением необходимости определения знака, но положительное значение left должно быть интуитивно понятным для всех, кто может использовать инструкцию поворота.

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

Мне нравится считать себя способным с помощью поворота инструкции и моя интуиция «Боже, почему это не скажет , в каком направлении это? Это, вероятно , ушли, но я буду смотреть на документации , чтобы убедиться.» Я согласен, это интуитивно понятно, что если бы вы собирались выбрать положительное направление, это было бы левое вращение, но есть гораздо более высокий порог для того, чтобы что-то было настолько очевидным правильным ответом, что на каждом месте вызова ясно, в каком направлении вы вращаетесь, не говоря Это.

Что касается удобочитаемости, как насчет чего-то вроде time.Duration API:

const RotateRight = -1

bits.Rotate(x, 5 * RotateRight)

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

@aclements Таким образом, вы

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

У меня нет проблем, если это будет [задокументированная] противоположность, но я бы не назвал это интуитивным.

@cznic биты пишутся справа налево, хотя

Я также за Rotate (https://github.com/golang/go/issues/18616#issuecomment-275016583), если мы заставим его работать в обоих направлениях.

В качестве контраргумента в адрес @aclements озабоченность по поводу направления: предоставление RotateLeft которое работает также при повороте вправо, также может дать ложное чувство безопасности: "Он говорит, что RotateLeft так что, конечно же, он не вращается. Правильно!". Другими словами, если он говорит RotateLeft лучше ничего не делать.

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

@nathany

биты пишутся справа налево, хотя

Биты - это просто двоичные цифры. Цифры в любом основании пишутся слева направо - даже в большинстве, если не во всех, системах письма справа налево. 123 - это сто двадцать три, а не триста двадцать один.

Другое дело, что степень множимого для цифры уменьшается вправо.

Еще раз: меня не волнует направление, просто интуитивное - дело личного воображения.

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

Пожалуйста, сохраните и RotateLeft, и RotateRight, вместо того, чтобы делать что-то, о чем половина разработчиков неправильно запомнит. Хотя, кажется, нормально обрабатывать отрицательные числа.

99% разработчиков никогда не запрограммируют инструкцию поворота, поэтому необходимость в однозначном указании в лучшем случае слабая. Достаточно одного звонка.

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

Я несколько симпатизирую аргументу о чистом дизайне, но кажется странным, что вам нужно удалить «Right» из «RotateRight», сохранив ту же реализацию, чтобы добиться этого. На практике кажется, что единственный способ ответить на вопросы - это заставить людей, которые его видят, читать документацию, с помощью вопросов, которые вызывает это название.
В конце концов, это вопрос однозначного направления и однозначного поведения для отрицательных значений. Отрицательные значения, вероятно, не должны вызывать беспокойства в общем случае.

Я говорю о том, что Rotate поднимает один вопрос для всех, отвечая на него косвенно через документацию.
RotateRight поднимает один вопрос к очень немногим людям, которые могут (и должны) читать документацию, если они им небезразличны.

С другой стороны, Rotate, вероятно, не позволит людям писать if n < 0 { RotateLeft(...) } else { RotateRight(...) } .

@ golang / offer-review обсудили это и в итоге получили только одну функцию, но назвали ее RotateLeft , а не просто Rotate , для большей ясности на сайтах обзвона. Отрицательные числа поворачиваются вправо, и документация проясняет это.

CL https://golang.org/cl/40394 упоминает об этой проблеме.

CL https://golang.org/cl/41630 упоминает об этой проблеме.

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

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

  • проверить, есть ли переполнение add / mul / etc
  • предоставить функции для реализации add / mul / etc, которые принимают входящий перенос и производят результат плюс перенос (или более высокое слово)

Лично я не уверен, что они принадлежат к пакету "бит" (возможно, тесты). Функции для реализации add / sub / mul с высокой точностью позволят реализовать на чистом Go некоторые из математических / больших ядер, но я не верю, что гранулярность правильная: мы хотим, чтобы оптимизированные ядра работали с векторами, и максимум производительность для этих ядер. Я не верю, что мы сможем добиться этого с помощью кода Go, зависящего только от «внутренних функций» add / sub / mul.

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

Я за добавление таких функций.

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

: +1: за закрытие этого вопроса и: heart: за проделанную работу.

Закрытие, поскольку возражений не было.

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

Rotate - специализированная функция; LTR или RTL актуальны только в контексте. @aclements поднял

Но вместо этого приходит сообразительность.

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

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

Я только прошу придерживаться контекста использования в будущем. Все это должно было быть самоочевидным с вопросами вроде «почему мы не предоставляем аналог RotateLeft, почему мы паникуем по поводу отрицательных шагов или обсуждаем int vs uint для шага»; в конечном счете, потому что я думаю, что от того, что означает «функция специалиста», просто отказались из-за неумелости.

Пожалуйста, избегайте хитрости в оправдании API. Это видно в этом обновлении 1.9.

Изменение https://golang.org/cl/90835 упоминает эту проблему: cmd/compile: arm64 intrinsics for math/bits.OnesCount

Была ли эта страница полезной?
0 / 5 - 0 рейтинги