All streams
Search
Write a publication
Pull to refresh
51
0.2
Valentin Nechayev @netch80

Программист (backend/сети)

Send message
> Согласитесь, вы же не будете говорить: «давай будем использовать целочисленную переменную (int, integer)», тем более когда есть ещё long и short(давай будем использовать «длинную/короткую целочисленную переменную»).

long и short, да, обычно так и произносятся, как и всякие int32, single/double, а вот «целое» стало обобщением всех целочисленных типов — в противовес строке, плавающему и т.п. Отсюда фразы «там целая, а не булевская», «урезать плавающее до целого» и т.п.

> cherry-pick

просто «чирикать». «Зачирикай эту подветку» :)
Нет, LWP это сущность для ядра, а нить — сущность для программиста. При модели M:N, ограниченное количество LWP (по одному на ядро плюс спящие на системных вызовах, детали чуть отличаются) исполняло произвольное количество нитей юзерленда, переключаясь между ними по необходимости. Эта модель стала выходить из употребления где-то после 2000 года, когда поняли, что 1:1 в современном мире проще и эффективнее, и тогда, например, FreeBSD и Solaris переключились на новую модель примерно одновременно.
Как исторический остаток, термин LWP мог остаться ещё много где, но он никогда не был основным для юзерленда.
Источник сейчас не найду, но за пределами общественно-политической лексики (для явлений, специфичных для СССР и России) и жаргона закрепилось только одно слово: polynya.
Может, и приврали. ;)
> А вот в обратную сторону это не работает — жителям США сложно жить с шипящими например.

Что-то не обнаруживаю никаких проблем, например, со словами типа genre, measure или Asian. Ни по произношению нейтивов, ни по словарям со звуком. В чём конкретно сложности?
А почему такое странное ударение?
Это волокна (fibers) внутри нитей (threads), так всяко лучше, кроме совместимости с древними багами неизвестно кого.
В Unix мире на моей памяти всегда переводили thread как «нить», а не как какой-то «поток». Последнее диверсионно, потому что путается одновременно и с stream, и с flow.
Какой вредитель ввёл «поток» как thread в 70-х — уже вряд ли установить, но от этого давно пора уходить.
> Решение-то очень простое: не покупайте и не садитесь за руль таких самосвалов (или языков).

О чём я и говорю тут давно.

> А если вам потребуется вся мощь этого самосвала (производительность этого языка),

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

> и радуйтесь жизни.

не получится — пока в системе есть хоть что-то на C/C++, доверие к ней уже страдает.
> Я пишу код, «положив с прибором» на все стандарты, учебники и прочие статьи (коих в интернете десятки), а компилятор, тем не менее, обязан меня «понять и простить»? Ну вот как вы себе это представляете?

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

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

> хотите иного поведения — напишите соотвествующий пропозал. Здравый смысл, он, знаете ли, у всех разный, потому в качестве ориентира ну никак не годится…

Если посмотреть, например, на трекер GCC, то там будет несколько сотен тикетов на тему «какого [...] оно взорвалось у меня в руках». Этого должно быть достаточно, чтобы увидеть проблему.
А конечные клиенты компилятора в лице прикладных программистов, скорее всего, не в состоянии написать ни один «пропозал» такого уровня, чтобы его стали рассматривать. Это не их дело и не их специализация, и не причина их высокомерно отвергать.

Именно поэтому Вы можете смело предлагать подходы в виде

> Если вы знаете хоть одного человека, который бы засабмитил хотя бы малюсенькое изменение в clang или gcc и который, по отношению к данному примеру, разделеяет мнение «давайте честно скажем, что в шланге багло» — то я готов изменить своё мнение и с ним уже, конкретно и предметно, обсуждать альтернативы.

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

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

Это будет значительно более корректной аналогией для undefined behavior.

> профессиональные продукты требуют к себе профессионального же отношения

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

Продукт, который взрывается, например, от арифметического переполнения от того, что изменился множитель в далёком заголовочном файле (и сделал это совсем другой коммиттер) — не должен считаться профессиональным.
> C и C++, Java и Javascript, PHP и Python

В Python это запретили при переходе на 3-ю версию. Для избавления от проблемы при переносе кода там сейчас префикс 0 вообще недопустим — синтаксическая ошибка. Явный префикс восьмеричной — 0o.
В Swift тоже 0o.
Есть языки, где вообще другая система знаков (например, Erlang — надо писать 8#123, 16#7b).

Так что процесс идёт в верном направлении — хоть и слишком медленно.
> Да и многих тут устраивает. Не устраивает только тех, кто понятия не имеет о том, как устроены и работают компиляторы —

И Вы тоже тут включили режим дʼАртаньяна, скопом записав всех возражающих в невежды.

> но почему их мнение должно кого-то волновать?

Хотя бы потому, что они — практические пользователи полученного. И позиция "сперва добейся" их не устраивает.
> микрокомандный исполнитель в 45000 транизисторов не впихнёшь.

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

> Хотите узнать что тогда Intel воротил, разрабатывая «совершенно новую» архитектуру — почитайте про iAPX 432.

Спасибо, в курсе. У Intel было несколько таких суперзакидонов, и притом очень интересных. А почему в кавычках?
Идея понятна, но мне кажется всё-таки не адекватной тому времени. Из тех же соображений могли бы сделать такие же подходы к основным операциям; стековая архитектура по сравнению с аккумуляторной даже выигрывала бы во многих случаях, особенно по простоте написания кода. Но основной 8086 всё-таки был сделан в классическом регистровом стиле.

Я склоняюсь к идее, что они пытались сделать разработку с точки зрения удобства компиляции. В то время (70-е годы) превращать код в польскую запись и исполнять её такой было типичным решением (см. P-code Паскаля, язык Forth, машины вроде Эльбрус-1,2, и тому подобное). Они знали стоимость подобного решения — заметное замедление по сравнению с чисто регистровым решением — и считали, что могут себе его позволить в случае FP операций. Гугление времён работы 8087 показывает, например, времена от 70 тактов на несчастый FADD (там что, внутри микрокомандный исполнитель был???). По сравнению с этим, цена манипуляций со стеком ничтожна.

А вот в следующем десятилетии наука заметно продвинулась, дав Static Single Assignment — который сейчас. Это было дороже (транслятор с ним мог не влезть в возможности 8086), но перевело всю логику на прямую работу с регистрами; с ней, наоборот, со стеком стало значительно сложнее работать. Поэтому последующие разработки (MMX, SSE) уходили от стека, и сейчас его сохраняют только в коде для разных VM, где удобно раскладывать из него на регистры без регенерации SSA. Ну а когда логику FPU ускорили до такой степени, что она стала чуть ли не быстрее целочисленной (как у AMD времён K5), а с OoO пришло переименование регистров, это дало окончательный приговор стеку…
В «соседней мастерской» не надувные, а реальные и делающие ровно то, что им задают. А в мастерской C — такие, что если на них хоть чуть-чуть ошибся, они через полчаса (когда ты давно завершил кусок работы и занялся другим) выстрелят, совершенно законно, тебе сверлом в спину.
У Slack тоже интерактивная страница — по ней можно двигаться и тормошить животных.
И да, и нет. Безусловно, большинство прикладных программистов не понимают, как работает компилятор; но им обычно это и не нужно, нужно иметь положительную часть опыта (как делать) и отрицательную (как не делать, где грабли). Но основное таки не в работе компилятора, а в том, когда он становится усилителем ошибок. Пример в исходном постинге темы не настолько характерен, как, например, этот; см. по тексту, как или изменение опций компиляции, или небольшая правка исходника, не меняющая суть выполняемого, сменяет неограниченный цикл на ограничение 3 итерациями в цикле. Вот это случай, когда возможность сделать UdB откровенно абьюзится авторами компилятора, а программисту найти такое, если кода много и/или оно закопано в макросах, может быть очень тяжело.

Ну а поскольку безошибочных программ вообще не бывает (helloworldʼы не считаем, и то неизвестно, что там в libc) — совершенно очевидно и обоснованно создаётся ощущение минного поля, авторы которого тут же с краю стоят и усмехаются — «ну-тко, кто ещё на что нарвётся?» А с соседнего поля кричат «а у нас мин нет, а ещё есть печеньки (вариант: батарейки)»…
Как раз сейчас не находятся (в основных командах сдвигов): они игнорируют старшие биты, а для результата «результат сдвига на N бит равен результату N сдвигов на 1 бит» должны не игнорировать.

Кодогенератор Go (он у них свой, доморощенный) реализует это следующим образом: пусть у нас вход:

        var a uint32
        var s uint32
        fmt.Printf("Params? ")
        fmt.Scanf("%x%d", &a, &s)
        r1 := a << s
        r2 := a >> s
        r3 := uint32(int32(a) >> s)
        fmt.Printf("%d(%x) %d(%x) %d(%x)\n",
                r1, r1, r2, r2, r3, r3)


Собственно вычислительная часть выходного кода выглядит так:

дизассемблер с пояснениями
e8 d5 a3 ff ff          callq  48d8b0 <fmt.Scanf>
48 8b 44 24 60          mov    0x60(%rsp),%rax ; &a
8b 00                   mov    (%rax),%eax ; a
48 8b 4c 24 58          mov    0x58(%rsp),%rcx ; &s
8b 09                   mov    (%rcx),%ecx ; s
89 c2                   mov    %eax,%edx ; a
d3 e0                   shl    %cl,%eax ; a << s машинный
83 f9 20                cmp    $0x20,%ecx
19 db                   sbb    %ebx,%ebx ; (s<32)?-1:0
21 d8                   and    %ebx,%eax ; r1 = a << s
89 44 24 54             mov    %eax,0x54(%rsp) ; for Printf
89 44 24 50             mov    %eax,0x50(%rsp) ; for Printf
89 d0                   mov    %edx,%eax ; a
d3 ea                   shr    %cl,%edx ; a >> s машинный
21 da                   and    %ebx,%edx ; r2 = a >> s
89 54 24 4c             mov    %edx,0x4c(%rsp) ; for Printf
89 54 24 48             mov    %edx,0x48(%rsp) ; for Printf
f7 d3                   not    %ebx ; (s>=32)?-1:0
09 d9                   or     %ebx,%ecx ; (s>=32)?31:s
d3 f8                   sar    %cl,%eax ; signed_a >> s
89 44 24 44             mov    %eax,0x44(%rsp) ; for Printf
89 44 24 40             mov    %eax,0x40(%rsp) ; for Printf


Код, как для современных процессоров, построен в стиле branch-free. Вычисляется «флаговое значение», которое равно -1 (все единичные биты), если сдвиг в пределах ширины переменной (32), и 0, если сдвиг равен этой ширине или выше; логический AND зануляет результат в случае слишком широкого сдвига. Для сдвига знакового значения вправо ещё хакеристее: так как результаты сдвигов на s>=31 совпадут со сдвигом на 31, это флаговое значение используется, чтобы сдвиг заменить на 31, если он больше, и дальше используется уже машинная команда.


И это то, что я хотел бы видеть по умолчанию для операции сдвига в любом языке уровня от C++ и выше. Ибо POLA и естественность для человека. А если кому-то нужно гарантировать оптимизированность операции — например, транслятор не может понять, что сдвиг будет в нужных пределах — предоставить какие-нибудь int::native_sll(arg, shiftcount), который будет builtinʼом транслятора.
> Какое имеет отношение «спека на Go» к C/C++?

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

> И ситуации, когда у вас будет быстрый, но небезопасный компилятор и медленный, но безопасный — у вас не будет. Ибо все компиляторы будут медленными…

— Моя программа работает в 4 раза быстрее твоей!
— Зато моя программа работает правильно.

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

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

«Джаза» как раз не будет из-за проверки ширины сдвига. Джаз это UdB, который разносит ту программу вщент, включая места, связь которых совершенно неочевидна. А дополнительная реакция достаточно дёшева, а если ещё и есть вычисленная гарантия, что сдвиг будет в пределах ширины, то её удалят.

> Просто потому что разные процессоры ведут себя по разному, но ни один (из распространённых) не ведёт себя так, как предписывает спека на Go!

Ха, ошибаетесь :) Делает, и самый распространённый. Почитайте для x86 доку на семейство PSL{L,R}{W,D}. Если сдвиг шире, чем одиночное значение в векторе, выходное значение обнуляется.

Я не знаю, зачем они это сделали, какой юзкейс стоял над ними. Могу только догадываться, что, как для всего MMX/SSE, они оптимизировали какой-то сверхважный частный алгоритм. Там много непонятного, включая мнемонику — они не стали повторять обычную скалярную x86, а взяли привычную для RISC. И, конечно, чисто формально это никак в данном споре не влияет на общий результат. Но говорить, что «ни один», нельзя.

А ещё Вы говорили рядом:

>> Например 32-битная единица, сдвинутая вправо на 33 бита на x86 даст 2, а на ARM'е — 0.

Проясните? Это тот же случай, или это ARM64 со сдвигами только двойными словами?

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

Да. Только этого ли хотели добиться, ужесточая наказания за ошибку?

> Да одних роутеров и «умных» лампочек, на которых никакие C#/Java и «не ночевали» больше, чем всех «платформ», на которых они работают!

Потому что никто не видел окупаемой цели в этом переносе. Но не по чисто технической невозможности.

> Остался только артефект: беззнаковое переполноение не существует, а знаковое — это UB. Но к этому, в общем, все уже привыкли.

Так в том и дело, что ой не все. И даже те, кто «в системе» 20+ лет, как я, натыкаются на заботливо расставленные детские грабли (детские — это те, что бьют не в лоб, а в самое чувствительное место). А уж что про новичков говорить. А есть ещё потоки выпустившихся из вузов, где ни один преподаватель не говорит в духе «запомните, здесь водятся супердраконы, съедят — не успеете мяукнуть», зато успешно тренируют алгоритмам и обращению с переменными. И они в значительной доле идут туда, где им никто не расскажет про проблемы, если они не читают хабр или аналогичные умно-заумные ресурсы.

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

А если говорить про уровень самого языка — в идеале то, что я хотел бы видеть, это некоторое расширение подхода, как в C# checked/unchecked, только с бо́льшим количеством вариантов. Например, для целых — выбирать раздельно по signed/unsigned исполнение арифметики: wrapping, checked, relaxed (как сейчас signed в C — гарантируется непереполнение), platform native, saturating (последнее — опционально). Тогда, записав
a = [[signed_checked]] (b+c); 

мы получаем конкретную желаемую программистом локальную политику, пусть даже это замедлит выполнение.
И, разумеется, всё это на уровне стандартного языка, а не расширениями вроде -fwrapv, и безотносительно общего уровня оптимизации.
Извините за почти бесплодные мечтания (по поводу C).

И спасибо за признание, что UdB для знакового переполнения — это именно рудимент от времён динозавров.

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

Про скорость всупереч корректности я уже сказал выше. Пока что всё это не отменяет того вывода, что сишники своими руками прогоняют тех, кто при относительно небольших усилиях авторов трансляторов мог бы остаться их пользователем. Да, я тут самонадеянно считаю, что описанные выше подходы управляемого уровня агрессивности транслятора реальны и подъёмны. Тот же -fwrapv существует уже много лет, чем эти опции хуже?
> Совершенно непонятно откуда возьмётся компилятор, что-либо делающий по-другому, если его некому разрабатывать. Ну вот совсем некому.

Так есть же кому. Только они в результате уходят и создают своё. Например, читаем спеку на Go:

>> For signed integers, the operations +, -, *, and << may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow. A compiler may not optimize code under the assumption that overflow does not occur. For instance, it may not assume that x < x + 1 is always true.

По последнему предложению видно, что это прямой наезд на подходы C/C++. И далее:

>> Shifts behave as if the left operand is shifted n times by 1 for a shift count of n.

и никаких тебе «если >= ширине сдвигаемого, мы включаем джаз». (В отличие от Java, C#, где заворот для знаковых целых определён, а вот правила для сдвигов уже как в C.)

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

А ещё в Java, C#, Go, etc. жёстко определено, что размерность целых — степень двойки, а представление отрицательных — дополнительный код. И это не мешает им работать на >99.99% реально существующих платформ, включая супер-embedded типа SIM-карт.

Зачем предполагать то, что в реальности уже не существует? Вы можете назвать хоть одно реальное железо, где остались бы отрицательные целые в обратном коде (1ʼs complement)? И почему, с обратной стороны, C завязан на двоичные биты? Почему (извините за провокацию) не рассчитывают на машины, у которых только десятичные цифры, или на троичные, типа «Сетунь»? По-моему, распространённость машин с обратным кодом примерно равна распространённости «Сетуни», то есть нулю.

А даже если там не 0 — то насколько это важно по сравнению с основной массой? Не лучше ли создать профиль, покрывающий практически всех?

> никто из людей, громко тут «бурлящих» не имеет ни малейшего представления о том, что у него «под капотом» и как оно там работает!

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

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

Information

Rating
2,744-th
Location
Киев, Киевская обл., Украина
Date of birth
Registered
Activity