Comments 269
Он стар и мало изменился за 45 лет
Я бы сказал что он Super Star!
Много технических низкоуровневых знаний в статье, но она от этого только лучше. Надо почаще возвращаться к истокам.
Регистр A — 64-битный доступ. RAX — 32-битный. EAX — 16-битный. AX — 8-битный. Младшая часть: AL, старшая часть: AH.
кажися правильно — RAX — 64-битный. EAX — 32-битный. AX — 16-битный. Младшая часть: AL — 8, старшая часть: AH — 8.
модифицировал код — заменил тип result на double, поправил printf скомпилировал с -О0:
$ time ./a.out /tmp/test
Чтение завершено, сумма равна 136901097048.000000
real 0m2.910s
user 0m2.728s
sys 0m0.180s
Написал аналогичную программу на lua:
local block = 1024
if #arg < 1 then
print(string.format("Usage:%s <input>",arg[0],#arg))
return 1
end
local f = io.open(arg[1], "rb")
local result=0.0
while true do
local bytes = f:read(block)
if not bytes then break end
local i
for i=1,#bytes do
result=result+string.byte(bytes,i)
end
end
print(string.format("Result:%f",result))
Запустил через luajit ( в установке torch):
time th test.lua /tmp/test
Result:136901097048.000000
real 0m2.063s
user 0m1.956s
sys 0m0.136s
делаем
#define BUF_SIZE 1024*1024
и компилируем с -O3:
time ./std /tmp/test
Чтение завершено, сумма равна 136901097048.000000
real 0m0.987s
user 0m0.884s
sys 0m0.100s
Теперь сравниваем с этой программой на lua + torch:
local block = 1024*1024
if #arg < 1 then
print(string.format("Usage:%s <input>",arg[0]))
return 1
end
local f = torch.DiskFile(arg[1], 'r')
f:binary()
f:quiet() -- disable error reporting
local result=0.0
while true do
local bytes = f:readByte(block)
if bytes:size()==0 then break end
local tensor=torch.ByteTensor(bytes)
result=result+torch.sum(tensor)
end
print(string.format("Result:%f",result))
time th test_torch.lua /tmp/test
Result:136901097048.000000
real 0m1.101s
user 0m0.828s
sys 0m0.284s
$ time gcc -O3 test.c
real 0m0.089s
user 0m0.076s
sys 0m0.004s
Мне кажется, что некорректно измерять JIT через time.
Э-ээ, правда?
Вы же для Си время компиляции не считаете?
Вы ещё время разработки предложите учитывать…
Программа на Си компилируется один раз — а запускается многократно, время компиляции «размазывается» по всем запускам.
Речь идет о том, что для jit надо прогревать код, что бы оптимизирующий компилятор начал оптимизировать горячие участки кода. Об этом к слову в статье упоминается.
Он будет получать билды с «прогретым» кодом?
обычно программисты сами вставляют кусочки кода, которые "прогревают" какой-то код при инициализации приложения, чтобы намекнуть JIT что мол "эта штука будет выполняться часто, оптимизируй ее".
Так что да, для конечного пользователя все будет хорошо, ибо часто вызываемый код будет работать быстро, а редко вызываемый код… не сильно будет влиять на общую картину.
Лет 6 назад сравнивал подобным образом C (gcc -O3) и Java (hotspot). Какая технология выигрывала — зависело от модели процессора.
Попробуйте проделать это на любом из высокоуровневых языков — вы и не приблизитесь по производительности к С. Даже на Java, с помощью JIT, с параллельными вычислениями и хорошей моделью использования памяти в пространстве пользователя.В своём недавнем выступлении на конференции Joker я как раз сравнивал Java и C на похожем примере. И там Java на малюсенькую долю секунды даже опередила C. Я заметил, что если число итераций цикла не константное, то GCC не делает loop unrolling даже с -O3. А JVM делает. И векторизировать по умолчанию умеет. А статистика, собранная в run-time, позволяет JVM выполнять спекулятивные оптимизации, на которые статические компиляторы не способны в принципе.
статические компиляторы не способны в принципе
это слишком сильное утверждение, статические компиляторы способны делать спекулятивные оптимизации, но это не супер удобно, ну и не получится в runtime пересмотреть принятые на этапе компиляции решения.
То что не может статический компилятор по сравнению с JIT-ом, это понять, что какой-то код в процессе работы из редко используемого превратился в часто используемый. Но спекулятивные оптимизации, о которых говорилось в оригинальном комментарии, статические компиляторы все еще могут выполнять и более того они это делают…
Во-вторых, мое замечание касалось ровно того момента, что статические компиляторы «не способны впринципе» на спекулятивные оптимизации — это утверждение просто не корректно.
В-третьих, у вас есть какая-то разумная статистика того кто этим заморачивается, а кто нет? Если есть поделитесь. Если же ваше утверждение базируется на том, что вы никогда не встречали тех, кто этим заморачивается, то об этом тоже стоит упомянуть ради объективности.
Во-вторых, элементарный поиск по документации выдал мне следующую ссылку:
https://msdn.microsoft.com/ru-ru/library/6t9t5wcf(v=vs.110).aspx
Из которой я понял, что NGEN используется в дополнение к JIT, а не вместо (если я понял не правильно поправьте меня), так что не очень понятно, на сколько это показательный пример того, что статическая компиляция с профайлом это редкая вещь.
Из более-менее распространенного ПО, где используется NGEN я могу назвать разве что Paint.Net, правда я не уверен, что они используют профиль.
То есть возможность-то есть, только ей почему-то никто не пользуется.
Во-вторых, как я понял, ваш комментарий можно интерпретировать так: «у меня нет никакой статистики об использовании, но я (почти) не знаю тех кто пользуется этой фичей» (серьезно, почему люди так тяжело указывать этот не мало важный момент)?
В-третьих, у меня есть догадка почему — NGEN позволяет ускорить запуск, а после запуска JIT уже сам разберется со сбором статистики и оптимизациями, которые на ней основаны. Другими словами профилирование на самом деле происходит, просто отложенно.
Отвечаю на во-вторых, так можно интепретировать любой ответ, например «у меня нет никакой статистики о вращении чайника по эллиптической орбите между Солнцем и Землей, и я (почти) не знаю тех, кто в него верит»,
Отвечая на в-третьих, NGEN полностью генерирует готовый код (как кстати следует из названия), после его работы JIT вообще не запускается на целевой машине.
так можно интепретировать любой ответ
Это не правда. Можно подкреплять ответ объективными фактами — туда-то туда-то был встроен такой-то такой-то счетчик и за такое-то время набралось столько-то, там-то и там был проведен опрос и из стольких-то ответивших столько-то ответили так-то и тд и тп — это объективные факты, про которые известно каким образом они получены.
А можно просто сказать, я никогда не видел то-то и то-то и поэтому я считаю, что того-то и того-то не существует. Это субъективное мнение, подкрепленное личным опытом одного конкретного человека.
И то и другое имеет право на существование, но полезно понимать чем мнение подкрплено/не подкреплено.
как кстати следует из названия
Вообще-то не следует, NGEN (Native Image Generator) — генерирует нативный код, но из этого не следует, что в процессе работы этот код нельзя заменить новым, сгенерированным на лету. Но впрочем ответ на свой вопрос я получил, спасибо.
Могу сказать что продолжать собирать статистику в иннер лупах в рантайме уже после свершенных оптимизиций (т.е. менять свой выбор) достаточно дорогостоящая операция и эффект от нее не очевиден, нужно инкрементировать счетчики на бранчах, что может сильно все замедлить. Тут все очень тонко.
Так же, когда есть ожидание смены нужного выбора — можно делать такие оптимизации и без JIT. Нужно завести 2 ветки кода с разными __builtin_expect и вести статистику вручную. Можно оборвать ведение статистики и передать управление веткам с отключенной статистикой.
Так же иногда можно собирать статистику отдельно и переключать разные преднастроенные __builtin_expect ветки, а статистику собирать или прекращать на основе каких-то факторов.
Ну я бы не сказал. Chrome под Windows собирается с PGO, например.
while ((read = read(f, buf, sizeof(buf))) > 0) {
if (read == sizeof(buf))
{
for (i=0; i < sizeof(buf); i++) {
result += buf[i];
}
}
else
{
for (i=0; i < read; i++) {
result += buf[i];
}
}
}
Цикл в if исполнится миллион раз (автор заявляет 1млрд байт). Компилятор сгенерирует разный код для того, что в if и того, что в else. Цикл с фиксированным числом итераций (sizeof(buf) это константа — 1024) компилятор развернёт на несколько циклов, т.е. например заменит на 128 итераций по 8 шагов в каждой, и развёрнутый код ещё дополнительно оптимизирует.
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
if (args.length < 1) {
System.out.println("Usage main <filename>");
System.exit(1);
}
byte[] buf = new byte[1024*1024];
FileInputStream is = new FileInputStream(args[0]);
int readed;
byte total = 0;
while ((readed = is.read(buf, 0, buf.length)) > 0) {
for (int i = 0; i < readed; i++) {
total += buf[i];
}
}
System.out.println("Result is:" + total);
}
}
и даже версия для JMH выдает аналогичные результаты
import org.openjdk.jmh.annotations.*;
import java.io.*;
public class MyBenchmark {
@Benchmark
@BenchmarkMode({Mode.AverageTime})
public byte testMethod() throws Exception{
// This is a demo/sample template for building your JMH benchmarks. Edit as needed.
// Put your benchmark code here.
byte[] buf = new byte[1024*1024];
FileInputStream is = new FileInputStream("/tmp/test");
int readed;
byte total = 0;
while ((readed = is.read(buf, 0, buf.length)) > 0) {
total += sumBuf(buf, readed);
}
return total;
}
public byte sumBuf(byte[] buf, int readed) {
byte total=0;
for (int i = 0; i < readed; i++) {
total += buf[i];
}
return total;
}
}
при этом для в C нет ручных оптимизаций, а только -O2
Это прям истинный пост настоящего адепта С.
Долго оптимизировал на низком уровне, потом выяснил, что компилятор все равно выигрывает. Это первый закон оптимизации — в простых случаях компилятор обыгрывает человека. Атаковать низкий уровень надо в самую последнюю очередь.
А в первую очередь надо атаковать высокий уровень.
Теперь вернемся в реальность и представим, что перед нами стоит именно такая, искусственная задача (а не реальное приложение, где низкоуровневная производительность не так важна).
Формулировка:
Прочитать файл размером гигабайт, посчитать его сумму.
Закон иерархии памяти утверждает, что доступ к каждому следующему уровню иерархии дороже на порядок (читать с диска в память примерно на столько дороже чтения с памяти в регистры процессора). Пока мы запускаем программу честно (в первый раз, не на кешированном файле), это подтверждается.
Таким образом, для максимальной оптимизации программы необходимо и достаточно, чтобы суммирование исполнялось одновременно с чтением с диска. Если это сделать, то скорость суммирования не важна (она всегда будет быстрее чтения). И тут выигрывает любой язык, в котором есть встроенные удобные потоки и асинхронность. Подсказка: это многие языки верхнего уровня, но не С :-)
Единственное, языки с JIT будут проигрывать бенчмарки мини-программ за счет времени на компиляцию, но достаточно обработать AOT-компилятором, чтобы это преимущество исчезло.
Подсказка: это многие языки верхнего уровня, но не С :-)
Для данной синтетической задачи надо всего лишь два системных вызова: создание потока и захват мьютекса (создание-удаление тоже, но это мелочь). Что вовсе не требует высоких абстракций, тут хоть на машинном языке пиши.
import sys
import numpy as np
block=1024
result=0
with open(sys.argv[1], "rb") as f:
while True:
bytes = np.fromfile(f, dtype='uint8', count=block)
if bytes.shape[0]==0 : break
result+=np.sum(bytes)
print("Result:{:f}".format(result))
$ time python test_numpy.py /tmp/test
Result:136901097048.000000
real 0m4.539s
user 0m4.384s
sys 0m0.232s
Если увеличить размер буфера до 1024*1024, то будет:
$ time python test_numpy.py /tmp/test
Result:136901097048.000000
real 0m1.007s
user 0m0.948s
sys 0m0.304s
Все равно код, выполняющий расчеты, написан на С.
Хотя это как раз показывает, на сколько удобнее питон для такого рода задач. И то, что каждому языку свое место.
Полагаю что даже чтением в память на питоне там и не пахнет :-)
Переполнения являются неопределённым поведением только если вы используете знаковые целые. Для беззнаковых целых переполнение по стандарту, вообще‐то, невозможно (C99, 6.2.5, параграф 9):
A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.
Правда, я, как и многие другие программисты, кого я видел, предпочитаю говорить, что для беззнаковых переполнение определено. Но в стандарте написано — нет, текущее поведение не является переполнением. Определения «overflow» я как‐то не нашёл, а всё зависит от него.
Очевидно, автор тоже считает, что текущее поведение беззнаковых целых описывается термином «переполнение». Но факт в том, что текущее поведение, во‐первых, определено, во‐вторых, не считается переполнением по стандарту. О том, переполнение это или нет, ещё можно спорить, но никакого неопределённого поведения там нет в принципе ни по стандарту, ни на практике. По стандарту есть только определяемое реализацией поведение: unsigned char
вовсе не обязан быть восьмибитным.
Замена чтения на:
readed = lseek(f, 0, SEEK_END);
buf = (unsigned char*)mmap(0, readed, PROT_READ, MAP_SHARED, f, 0);
ускорило у меня в 3 раза (при условии, что файл уже в кешах).
Вопрос: каков предел информации для статьи, ориентированной на знакомство, а и так затронуло уже множество аспектов?
Простите, но он уже разрабатывается на них. Скетчи для ардуино и других подобных платформах пишутся на высокоуровневых скриптовых языках. На низкоуровневых пишутся разве что драйвера и ядро.
Кстати, исключение не единственное. Есть еще ряд микроконтроллеров, которые программируются на js, lua (при наличии соответствующей прошивки), C#. Что, впрочем, не отменяет факта существенно большей популярности более других плат.
https://www.arduino.cc/en/Reference/HomePage
https://www.arduino.cc/en/Reference/WiFiServer
А в недрах их IDE еще находится файлик с функцией main, который перед сборкой присобачивается к проекту. И в этом main находится вызов того самого setup и тот самый цикл, который дергает loop
Программы многих школьных курсов по программированию до сих пор начинаются с освоения азов ассемблера и С.C и ассемблер в школе? О_о
а что? 8080 вполне осваивается школьником
Си легче с++ и с#, а 8ми битный ассемблер очень простой, проще тригонометрии.
Я вообще не понимаю почему в школе не учат двоичной системе, логике и комбинаторике.
Не холивара ради (сам олимпиады на C писал), но почему бейсик подвинули?
2. А вообще, по хорошему, надо начинать не с си или паскаля, а классе в 5, с какого-нибудь скретча или ему подобного.
3. Есть у меня такая мысль, что в школе учить надо не языку, а программированию. (Тут ещё одна проблема, не забывайте, что школы общеобразовательные и 90% детей всё равно что вы там преподаёте).
4. У нас линукс на всех машинах, сам Бог велел не на паскале писать, а на си.
5. Спор между си и питоном был бы более актуален, а паскаль имхо, точно надо на свалку истории отправлять.
Да потому что для обычного человека (таковыми являются большинство) высокоуровневые вещи проще низкоуровневых. Как в математике, так и в программировании. В математике под низкоуровневыми вещами я понимаю логику (на ней всё держится), а высокоуровневыми — все остальные разделы (они понятнее для большинства, так как, взять ту же геометрию, более наглядны). Ну а в программировании, понятное дело, низкий уровень — это всякий ассемблер, а высокий — всякие скриптовые языки. Вторые понятнее для большинства. В том числе потому, что на них можно быстренько сделать что-нибудь наглядное. Сайт. Программу, рисующую график или какую-нибудь анимацию. Игру. И так далее.
Даже у нас на мехмате МГУ математическая логика далась студентам хуже всего.
А один вузовский преподаватель-математик (!), правда, не из МГУ, сказал мне, что не лезет в основания математики глубже некоторого уровня. "Вот здесь вот я для себя поставил черту, глубже я не лезу" — сказал он в ответ на мои многочисленные вопросы о математической логике.
Так что делаю вывод, что, наверное, из миллиона людей только несколько штук имеют до такой степени аналитический склад ума, что им математическая логика проще всего остального
Думаю, в плане трудозатрат для обучения, как высокоуровневые, так и низкоуровневые технологии примерно одинаковы. Просто у них совершенно разные ниши.
сравните функционал каких-нибудь регулярных выражений с кодом на си
Особенно забавно, что интерпретатор этих регулярок в случае большинства скриптовых языков будет на Си. И никто не мешает на Си взять какую-нибудь классическую pcre.
Это конечно же :)
Функциональность языка почти всегда можно расширить библиотеками, а если и их недостаёт по каким-то причинам, то и вовсе писать на языке более высокого уровня, который в свою очередь будет так же написан на языке более низкого.
Я лишь привёл регэкспы как некоторый пример высокоуровневого инструмента. Код с их использованием получается максимально лаконичным (и простым), что делает их мощным средством разработки, однако это совершенно не означает прозрачность (то есть, простоту) их реализации.
Идейная простота ассемблера вовсе не подразумевает простоту использования его в качестве языка программирования
Обычная путаница между «сложно» и «тяжело». Сложно научиться управлять экскаватором, а копать — легко. Копать лопатой — просто и тяжело.
Мне вот трудно далось. Но я сам мыкался. Попробовал Паскаль (Бейсик в базе уже был), как-то легче пошло. Через год снова попробовал C… и не понял, чего я сразу не понял :) Но это уже было классу к 9-10.
Выпустился в 2013. Ассемблера не было, но программа была сильная:
8 класс — языки Си и Паскаль, основные структуры данных.
9 класс — только Си, более сложные структуры и алгоритмы
10, 11 классы — С++, рисовали всякие интересности с использованием OpenGL.
При этом за говнокод можно было и по ушам получить и пойти переписывать. И ещё кружок этот же преподаватель ведёт, там гораздо сложнее программа. Так что школы разные бывают.
К тому же школьник-школьнику рознь. Вот, например ~8 класс.
https://github.com/intelfx/Homework_2011
Например, Fortran, относящийся к той же «возрастной группе», что и С, в некоторых специфических задачах более производителен.
Щито?
Фортран лет на двадцать старше Си — от чего не имеет ряда полезных фич.
Плюс «моделей ассемблера» существует множество, не сводимое к AT&T и Интел, GAS — это GNU assembler.
Для ассемблера Вы используете AT&T синтаксис — что довольно специфично, fasm и masm юзают Интел.
Последний в x86 вообще более распространён, для которого характерны заметно другие подходы в синтаксисе: — т.е. «доллары» и «проценты» там не используются.
Ну и вообще, в других ассемблерах в качестве маркера «непосредственного значения» может использоваться '#', регистры пишутся непосредственно, без «процента», а «разыменовывание указателя» (косвенное обращение к памяти) может так же обозначаться знаком '@'.
P.S. Да, когда-то регистры общего назначение так же именовались «свероперативным запоминающим устройством».
Все оптимизации в языках высокого уровня (C это ЯВУ) сводятся до "давайте сделаем это на ассемблере и придумаем как обмануть компилятора, чтобы генерировал наш код".
А почему? Компилятор, он не священная корова. Зачем нам посредник в общением с богом процессором? Написать все на ассемблере и все дела. Я именно так и делаю. Код получается не хуже чем на C, а иногда даже лучше. Вот отзыв одного удивленного программиста после просмотра моего ассемблерного кода (кстати веб приложение):
Just had a cursory peek at the source code. What struck me most strongly is that the number of LOC that are not direct calls to Sqlite is quite low, the overall size of the project is very small, and that it's remarkably easy to work out what the code actually does.
Написать на ассемблере программу, которая будет сегодня работать, чем программа на C — не проблема. Написать её так, чтобы она нормально работала на процессоре, который ещё не разработали… лет так через 10… нереально.
А программы «на выброс» можно на чём угодно писать — хоть на Brainfuck'е.
А я на ассемблере пишу уже около 30 лет. И программы написаны примерно в 1987-ом, все еще работают нормально. Правда, они под ДОС, но что поделать, ни Линукс ни Windows тогда не было.
А вообще, каждая программа нуждается в поддержке. И на C/C++ и на ассемблере. Компиляторы меняются, стандарты языка меняются. И заметьте, они меняются намного быстрее, чем процессоры. Так что, через 10 лет, программа на C/C++ тоже вполне вероятно не скомпилируется сразу, а понадобиться пошаманить над исходниками.
«Правда, они под ДОС»
Нет.
Правда, они под ДОС, но что поделать, ни Линукс ни Windows тогда не было.
Вот. А программы написанные на Си (без вставок на асме) — будут работать, только перекомпилировать надо. И что самое забавное — даже если программа была бы написана на какой-нибудь ЕС ЭВМ, она тоже бы перекомпилировалась и заработала. С огооврками, конечно, но с ассемблером не сравнить.
П.С. сам — фанат Ассемблера, но надо и меру знать.
А программы написанные на Си (без вставок на асме) — будут работать, только перекомпилировать надо
Ну, это вряд ли. Работать будут только очень, очень простые программы. И те, которые изначально писались чтобы были 100% переносимые. А это наверное 3% от всех сишних программ. И даже те, вряд ли можно перекомпилировать просто так на современных компиляторов.
Ну, почему сразу "вовсе"? Конечно, немножко придется подкоректировать исходники, но общая канва программы будет та же самая и вполне заработает например в Линукс.
Конечно, немножко придется подкоректировать исходники, но общая канва программы будет та же самая и вполне заработает например в Линукс.Не будет. Операционка использующая Long Mode не совместима с 16-битными режимами. В принципе. Ни Windows x64, ни Linux.
Представить себе кусок кода, который можно выдрать из 16-битной программы и всунуть в 32-битную программу можно — но вряд ли такое можно было написать 30 лет назад, до появления 32-битных процессоров.
Да, можно поставить VMWare, можно DoxBox — но после того, как вы начинаете вкручивать в вистему всё это хозяйство разговоры о том, что всё «просто и понятно» можно уже всёрьёз не воспринимать.
Так он и на ARM не пойдет. Просто надо написать ту самую программу для ARM. И будут нам всем две отличные программы, наместо одну посредственную.
А вот и нет. Это вообще-то устарелое мышление. В нулевых годах так и было, когда прогресс в железе шел быстрым ходом и софт не успевал за ним, а новые процессоры исправляли медленный софт очень быстро.
Те времена прошли. Были, да сплыли. Теперь процессоры уже не станут быстрее. А потребители хотят чтобы софт становился лучше. Чтобы не ждать все время и чтобы программы не висли и результат давали, ну вот сейчас.
Параллельные алгоритмы и многоядерные процессоры дадут некоторое время на перегруппировку, но далеко не все алгоритмы работают параллельно.
Так что учите ассемблер. Пригодится.
Так что учите ассемблер. Пригодится.
Сбавьте тон, не красит. Не уверен, что вы знаете его лучше меня.
Не уверен, что вы знаете его лучше меня.
Я тоже не уверен. Давайте разберемся… В конце концов, я программы на ассемблере пишу постоянно и публикую в сети:
Вот — IDE для FASM на ассемблере. Сам сайт сделан на CMS на ассемблере. А еще есть у меня высокопроизводительный форум на ассемблере.
А можно чего нибудь вашего посмотреть, чтобы было на ассемблере?
Где применяется сайт и форум на ассемблере? Какие задачи он решает?
Вы же должны понимать, что удобство разработки, скорость разработки, и вовремя исправленная уязвимость — это тоже высокая производительность, которую нужно принимать в расчет, а не только мерять скорость отлика на форуме с 100 уников в день, с чем легко справляется даже юкоз?
Процессоры становятся быстрее. Просто гонка пошла не в частоту, а в новые инстркуции, в количество ядер.
Все это, просто судороги умирающего закона Мура. Недолго продлятся.
Вы же должны понимать, что удобство разработки, скорость разработки, и вовремя исправленная уязвимость — это тоже высокая производительность,
Так, да не так. Потому что суммарное время выполнения программы намного выше чем время разработки (а если наоборот, то такую программу и обсуждать не стоит). Поэтому стоит поработать немножко больше, экономя время потребителей.
Да, понятно что время разработки, оно ближе к телу разработчика, а время выполнения — оно там, при потребителей случается. Куда они денутся? Подождут немножко. А программа загружается 30 секунд? Ничего, пусть не закрывают. А ждать приходится сто раз, пока откроется окно — ерунда, вот у меня стоит супер-пупер-зверь машина и у меня не тормозит. Пусть они купят еще 32GB RAM и все будет ОК.
А вот, возьмут и уйдут там, где их время не разбрасывают и ценят.
А между прочем, время разработки на ассемблере не так и большое. Посмотрите на мой проект форума (ссылка наверх) — зайдите в хранилище кода и посмотрите сколько время оно мне заняло. Оно не так уж и большое.
Время исправления багов и уязвимостей также намного ниже ожидаемого. (кстати, уязвимости в ассемблерном коде почти и нет, потому что он проще и чище)
Поглядите немножко на исходниках — там все читается просто и понятно.
Не так страшен черт как его малюют. :)
Где применяется сайт и форум на ассемблере? Какие задачи он решает?
А жрет он ресурсов очень и очень немного. И CPU и памяти. А на хостинге, они оплачиваются и недешево.
По моим прикидкам, при всех равных условиях, форум на ассемблере может обслуживать от 5 до 10-ти раз больше посетителей, чем такой же, но на PHP.
Вы смогли договориться с каким-нибудь из хостеров, чтобы именно ваш движок был насильно установлен у всех клиентов?
Вы смогли придумать, как быстро вы сможете исправлять уязвимости на вашем сайте и хостинге, написанные на ассемблере?
Вы смогли прицепить апи и документацию, чтобы к вашему сайту и форуму на ассемблере люди могли легко добавлять плагины?
Вы никак не хотите понять, что вы меряете попугаев, а не реальный прирост?
«Все это, просто судороги умирающего закона Мура. Недолго продлятся.»
Вы никогда не пробовали потестировать с какой скоростью пережимается видео на процессоре 5-летней давности и на шестом поколении от интела, которое по частоте в полтора раза ниже? И сколько при этом тратится энергии?
Очень Вас прошу, научитесь адекватно воспринимать тот факт, что метрики измерения разные нужны, и разные важны. А пересчет исключительно тактов процессоров — это всего лишь один, и не такой важный на сегодняшний день.
с какой скоростью пережимается видео на процессоре 5-летней давности и на шестом поколении от интела
А что это типичная задача?
Был у меня процессор
Амд 4400+ 2гб озу, C# проект билдился 30 сек,
Купил i5-3570k + 16гб озу, более быстрая память, диск ssd — проект билдится 25 сек. БД работают так же. Браузеры так же, IDE так же.
Ранзица в 7 лет, прироста скорости повседневных задач = 0.
Апгрейд сделал только ради памяти, так как просто нужно было запускать кучу виртуальных машин для теста, кроме памяти, на 100% хватало древнего атлона.
С учетом наличия конвеера и кеша во всех процессорах, на мой взгляд, на производительность влияет только частота проца и памяти, которая уже давно практически не растет. Растет только маркетинговый булшит.
Я согласен что некоторые специализированные команды в процессорах появляются, которые что то ускоряют, но это что то — малый процент от задач.
Вы не ответили на вопрос, где именно применяется ваш сайт и форум на ассемблере, кроме как на нем висит лично ваш блог?
Мне это достаточно. Стоит мне копейки (ну 2.50€ в месяц). Всякие Слэшдот-эффекты не грозят (а были). А монетизировать все это мне совершенно недосуг. Скачивайте и пользуйтесь на здоровье.
Вы никогда не пробовали потестировать с какой скоростью пережимается видео на процессоре 5-летней давности и на шестом поколении от интела, которое по частоте в полтора раза ниже? И сколько при этом тратится энергии?
Крохи, все это! Крохи! Ну сделают еще в 5 раз быстрее, ну и что? Экспоненциальный прогресс уже не будет! Вы разницу между экспоненциальной и линейной функцией знаете?
Забавно, что не уточнялось, о каком именно мнемокоде идет речь.
Я не пишу Open Source ПО, уж извините. Все, что пишется по работе, включая куски на ассемблере, закрыто. В моем любительском активе — любительская ОС на ассемблере, а также отдельный язык ассемблера, реализованный в моем микрокоде для моей модели процессора своей архитектуры.
В конце концов, я программы на ассемблере пишу постоянно и публикую в сети
Это не показатель хорошего знания. Можно уметь играть в шахматы, но не уметь выигрывать. Надеюсь, аналогия ясна? Закодировать в мнемокоде простыней какой-либо алгоритм, даже большой, уровня CMS-IDE по шаблонам могут многие (например, самый примитивный кодогенератор самого примитивного компилятора ЯВУ), это не то, что называется «знать ассемблер».
Я могу посмотреть ваши исходные тексты, проблема лишь в том, что критика с моей стороны будет наверняка заведомо воспринята как «а ты кто?», а это скучно.
Я могу посмотреть ваши исходные тексты, проблема лишь в том, что критика с моей стороны будет наверняка заведомо воспринята как «а ты кто?», а это скучно.
А я, вообще то, критики не боюсь. И не потому что мой код совершен, а потому что это один из путей сделать его лучше. Но конечно, это возможно только если критика конструктивная.
Дело в том, что все так, а результат в целом, все таки нельзя сделать на ЯВУ. Никак не получится. Ну, не считая, если переводить от ассемблер на C 1 в 1. Да и тогда результат будет ужасен как читаемость кода, больше и несколько медленнее.
а результат в целом, все таки нельзя сделать на ЯВУ
Почему?
Есть ли куски кода, критичные по скорости или размеру, которыми вы гордитесь в силу применения нестандартных решений, до которых не может дойти компилятор, для расширения моего кругозора?
Почему?
Сложно объяснить. Главное, потому что на ассемблере, человек программирует не так как на ЯВУ. Кстати, поэтому ни в коем случае ассемблер нельзя учить изучая результаты компиляции.
Есть ли куски кода, критичные по скорости или размеру, которыми вы гордитесь в силу применения нестандартных решений, до которых не может дойти компилятор, для расширения моего кругозора?
Есть такие куски.
ваш форум написан на ассамблере и пишет штуки вроде "Script run time: 6.598 ms".
мои проекты на php, который рестартует после каждого запроса, написан на Symfony, использует жирную ORM и т.д. и время обработки запросов всего-то 30ms.
А если я возьму какой-нибудь nodejs для сравнения? Ему не нужно "умирать" после каждого запроса, все будет крутиться в памяти. По сути мы тут упремся скорее в работу с базой данных нежели в nodejs.
Да и тот же php, можно взять php-pm или uwsgi и разогнать это добро до уровня 10ms, что уже сравнительно одинаково.
А теперь самое интересное. Итоговое время загрузки страницы вашего форума на ассемблере составляют 300ms. То есть обрабатывается запрос за 10ms или за 30ms — это уже не столь важно, поскольку по итогу для конечного пользователя запросы обрабатываются одинаково быстро/медленно.
Ну и еще штука… попробуйте подсчитать сколько стоит поддержка кода на ассемблере и на каком-нибудь javascript/php. А теперь прикиньте сколько часов нужно на разработку форума? А теперь прикинем разницу в часах и попробуем посчитать, насколько серверов у нас хватит сэкономленных денег.
А теперь самое интересное. Итоговое время загрузки страницы вашего форума на ассемблере составляют 300ms. То есть обрабатывается запрос за 10ms или за 30ms — это уже не столь важно, поскольку по итогу для конечного пользователя запросы обрабатываются одинаково быстро/медленно.
Это имеет значение для моего кармана, когда оплачиваю хостинг.
Ну и еще штука… попробуйте подсчитать сколько стоит поддержка кода на ассемблере и на каком-нибудь javascript/php.
А мне на ассемблере дешевле, потому что я ассемблер знаю, а js/php нет.
Да и никакая особая поддержка не приходится делать. Не знаю как там на php, может они все время ковыряют в коде, исправляют баги и уязвимости затыкают.
На ассемблере такие страсти не бывают. Были некоторые баги, которые исправил во время альфа и бета тестов и все. Теперь, программировать придется только когда понадобится новую функциональность добавлять.
Теперь, программировать придется только когда понадобится новую функциональность добавлять.
Здесь «собака и порылась». Расширять/править php дешевле.
А мне на ассемблере дешевле, потому что я ассемблер знаю, а js/php нет
В данный момент — да, но если изучить php, то вы быстро отобьете время, на это потраченное, и улетите вперед по сравнению с собой же старым.
В данный момент — да, но если изучить php, то вы быстро отобьете время, на это потраченное, и улетите вперед по сравнению с собой же старым.
Нет не улечу вперед. Я оттуда возвращаюсь.
Изучение php с каким-либо фреймворком на начальном уровне потребует недели вашего времени, а написание программ на нем будет идти быстрее просто в силу большей выразительности языка.
Я оттуда возвращаюсь.
Это что? Argumentum ad verecundiam?
У него интерес. Он же компиляторы на C++ и D разрабатывает. Конечно что так говорит будет.
Но кстати, если разработка ведется на C или C++, то наверное проблема несколько легче. Но когда такое начинают говорить программисты на всякие Питоны, тогда звучит и правда смешно.
if (argc != 2) { fprintf(stderr, "Использование: %s \n", argv[0]); exit(-1); }
вероятно, должно быть
if (argc != 2) {
fprintf(stderr, "Использование: %s FILE\n", argv[0]);
exit(-1);
}
;)
Вы действительно думаете, что интернет вещей будет разрабатываться на высокоуровневых языках? А будущие видеокодеки? VR-приложения? Сети? Операционные системы? Игры? Автомобильные системы, например автопилоты, системы предупреждения о столкновении?
Все это если не уже пишут на высокоуровневых языках, то точно в будущем будут это делать (как минимум бОльшую часть таких решений). Эффективность требует выносить низкоуровневые часто используемые вещи в высокоуровневые абстракции. В этом случае КПД может быть и не будет равен единице, но высокоуровневые решения всегда будут к этому значению стремиться. И в какой момент плюсы от высокоуровневого подхода начнут значительно перевешивать минусы пониженного КПД.
Хотя конечно всегда будет существовать класс задач, где необходимо выжимать максимум из железа. Но их будет исчезающе мало относительно всех остальных задач.
Все это может быть только если быстродействие компьютеров повышается быстрее, чем сложность программ, которые на них выполняются.
А закон Мура уже не действует. Откуда лишней производительности взяться?
Некоторые даже считают что In 9 years Assembler will return to mainstream.
Некоторые даже считают что In 9 years Assembler will return to mainstream.
Это просто тупо мнение гражданина в его блоге. Без каких-либо доводов. Не считать же доводом «ассемблер будет нужен, потому что нужна высокая производительность». И так понятно, что есть места, где он нужен.
Но с чего этот товаристч решил, что именно через 9 лет мы дойдём до передела — мне не очень ясно.
Ассемблер вернётся когда Закон Мура прекратит своё действие
Уже.
Насколько я понимаю, там проблемы с теплоотводом. Напихать вдвое больше транзисторов в ту же ширину можно, заставить одновременно работать вдвое больше транзисторов — нет. Причём это проблема уже с одним слоем: https://habrahabr.ru/company/intel/blog/158223/. Т.е. третье измерение получается нужным только для того, чтобы отправить слабоиспользуемые (отвод тепла с одной стороны эффективнее, чем с другой) блоки поглубже и немного повысить энергоэффективность — как за счёт увеличения числа транзисторов (тот же принцип, что обуславливает существование «тёмного кремния»), так и за счёт уменьшения длины проводников. Или сделать систему компактнее для какой‐либо специфической задачи (скорее всего, не относящейся к потребительскому сегменту). Но никак не ускорить что‐либо. Перед освоением третьего измерения для ускорения процессоров нужно что‐то сделать с тепловыделением, а прорыва здесь пока не видно, хотя он возможен.
Перед освоением третьего измерения для ускорения процессоров нужно что‐то сделать с тепловыделением, а прорыва здесь пока не видно, хотя он возможен.Ничего не нужно делать. Нужно просто вспомнить что повышение частоты вдвое увеличивает тепловыделение в восемь раз. Так что — да, по мере увеличения числа слоёв частоты процессоров будут падать. А головная боль разработчиков — будет расти.
Но тут как бы мы уже по проторенной дорожке идём: с тех пор, как производительность одного ядра упёрлась в потолок и стали наращивать количество ядер. Топовые Xeon'ы с десятками ядер работают на более низкой частоте, чем многие более дешевые ЦПУ.
Ассемблер вернётся когда Закон Мура прекратит своё действие.
Вряд ли. В глобальном плане эффективнее по трудозатратам вкладываться в продвинутые оптимизаторы.
В наш век интернета Один раз реализуется некоторая оптимизация в компиляторе — и её могут использовать все.
А продвинутых программистов, знающих все тонкости (или достаточно тонкостей, чтобы выиграть у оптимизатора), из-за увеличения сложности систем очень мало, и работают они медленнее компилятора.
Угарел с картинки :-)
Эти системы будут писать в первую очередь на надежных языках, именно поэтому на языках высокого уровня.
Ada, Java, C#.
В том числе и на С, и на ассемблере, и на новомодных языках, которые пока еще не придумали.
Интересно, скоро ли будут писать такое на Rust.
Иначе это так и не станет дёшево и не появится в каждой кредитной малолитражке.
Cогласно классификации: С — высокоуровневый, процедурный. Таки не Ассемблер же.
На основании этого привык считать язык С языком высокого уровня.
Хотя, конечно, согласен, высокоуровневость — понятие относительное. И вполне допускаю возможность введения новой классификации. Но в этом случае принцип разделения языков стоит обозначить явно, ибо принятую классификацию вроде как не отменяли (по крайней мере я такого не помню).
Разница в этой классификации в том, как именно человек пишет код (ну во всяком случае, как я это вижу).
Вы при работе с массивами в C напрямую взаимодействуете с адресами в памяти, с регистрами? В машинном представлении?
Подозреваю, что вы обращаетесь к элементам массива по индексу, да еще и через приятного вида синтаксис, и работа с ними не особо отличается от той же Java, кроме того, что С нужно следить за выделенной памятью.
В ассемблере (символьное обозначение машинных команд) же само понятие массива весьма эфемерно и его еще нужно, так сказать, смоделировать. Т.е. вам нужно через низкоуровневые операции с памятью, реализовать то, что есть в С «из коробки»
2.
Ну да, ну да, только вот у сишных массивов нет контроля границ, длину надо таскать явно вместе с указателем — в общем, в Си нет массивов.— из этой фразы вообще непонятно как сделан вывод о
в Си нет массивовРасскажите пожалуйста, чем же являются эти пресловутые массивы в других ЯП?
Если у языка «опасная» архитектура и повышенное требование к внимательности и рукам разработчика, это автоматически делает его более низкоуровневым и трушным?
3.
То, что некий адрес и целое число являются массивом — плод воображения разработчика, не болееВсе эти ваши дататайпы, структуры и объекты + синтаксический сахар — плод воображения разработчика, так сказать — human interface. Есть только 1 и 0, да и то это человеческая абстракция. На самом деле есть сигналы и физическое состояние транзисторов — вот это прямо таки самый «низкий» уровень (но не конечный), <irony> когда научитесь менять вручную и контролировать состояние каждого транзистора, тогда будете гуру «низкоуровневости» </irony>
но называть высокоуровневым язык, в котором, например, вместо массивов и строк — адреса в виртуальной памяти (!), можно только забыв про 90% существующих языков программирования.
На этом основании вы говорите о том, что С не является высокоуровневым.
Потом мы цепляемся за реализацию массивов.
Ну да, ну да, только вот у сишных массивов нет контроля границ, длину надо таскать явно вместе с указателем — в общем, в Си нет массивов.
По прежнему не понятно, почему заявляется о том, что в С отсутствует этот сложный тип данных.
Комментарий про «опасную» архитектуру так же относится именно к этой фразе.
int[] arr = new int[10];
int* arr = (int*) malloc(10 * sizeof(int));
Видимо вас очень беспокоят динамические массивы в С.
В первом случае тип переменной — массив целых чисел. Во втором — указатель
Первый случай.
Ваш пример — это статический массив:
int[] arr = new int[10];
Оператор new указывает на то, что каждый элемент массива получает значение по умолчанию. Каким оно будет определяется на основании типа данных (0 для int). Динамические массивы в Java реализованы через классы Vector и ArrayList и для управления элементами эти классы используют методы интерфейсов Collection и List.
ArrayList <Integer> i = new ArrayList<Integer>();
i.add(3);
i.add(new Integer(34));
System.out.println("i size: "+i.size());
System.out.println("i elements:"
+i.get(0).intValue()+", "+i.get(1));
Статический массив С:
int arr[3] = {0, 1, 2};
Статический массив Java:
int arr[] = {10,20,30};
Как видите, разница даже в синтаксисе невелика
Ну, напомню, что в Java все сложные типы данных ссылочные, в Java массивы являются объектами. Это значит, что имя, которое даётся каждому массиву, лишь указывает на адрес какого-то фрагмента данных в памяти. Кроме адреса в этой переменной ничего не хранится. Индекс массива, фактически, указывает на то, насколько надо отступить от начального элемента массива в памяти, чтоб добраться до нужного элемента. Просто в Java нет явных указателей, и в нормальных условиях, ручного управления памятью.
Второй случай.
int* arr = (int*) malloc(10 * sizeof(int));
Выделяется 10 блоков по sizeof(int) байт каждый, к элементам массива можно обращаться как через индекс так и через разыменование arr[i] и *(arr+i).
Здесь речь идет только о динамическом массиве, который вы пытаетесь сравнивать со статическим Java. В C динамические массивы реализованы через явные указатели, за которыми действительно сложно следить.
Первый я могу передать в другой метод или вернуть из другого метода без хаков и костылей, второй — нет. «Массив» в Си — это паттерн, а не элемент языка.
Жалуетесь на архитектуру языка.
Передергиваете. Меня не особо волнует, что происходит, когда я пишу
Главное — как с этим работать на выбранном мной уровне. Это называется абстракцией.case class Person(firstName: String, lastName: String)
Не передергиваю. Вспомните, изначально речь шла о «низкоуровневости».
И это ваша фраза раскрывает просто все.
Только один ответ может быть на такое — очень жаль.
Как можно называть язык низкоуровневым, когда за вас все делает компилятор, выдавая оптимизированный по своему алгоритму машинный код, производительность которого может довольно сильно варьироваться в зависимости от компилятора и его настроек. Так же напоминаю, что для действительно низкоуровневых операций в С делают ассемблерные вставки.
PS
Просьба не делать выводов типа таких:
«но называть высокоуровневым язык, в котором, например, вместо массивов и строк — адреса в виртуальной памяти (!), можно только забыв про 90% существующих языков программирования.»
не имея достаточных оснований. Еще больше поражает, что такие заявления еще и «плюсуют»
Статический массив С:
int arr[3] = {0, 1, 2};
Статический массив Java:
int arr[] = {10,20,30};
Как видите, разница даже в синтаксисе невелика
C99 вполне себе позволяет написать int arr[] = {0, 1, 2}
. И даже int arr[] = { [1]=1, [2]=2 }
. Указывать длину было обязательно только в предыдущих версиях стандарта (ну, или при отсутствии начального значения).
Пока вы не покажете мне место в стандарте Си, где специфицирован тип данных «массив», у которого есть атрибут «длина», обсуждать эту тему нет смысла.
1. Почему длина совершенно обязательно должна быть аттрибутом объекта, а не частью типа например?
2. Если оставить этот вопрос в стороне то вот вам:
An array type describes a contiguously allocated nonempty set of objects with a
particular member object type, called the element type. 36) Array types are
characterized by their element type and by the number of elements in the array. An
array type is said to be derived from its element type, and if its element type is T, the
array type is sometimes called ‘‘array of T ’’. The construction of an array type from
an element type is called ‘‘array type derivation’’.
An array type of unknown size is an incomplete type. It is completed, for an identifier of
that type, by specifying the size in a later declaration (with internal or external linkage).
Другими словами даже для языка C масив и указатель — это разные типы. Разница в типах скрывается из-за неявного приведения, но в языке C масив это явно выделяемый стандратом тип. Хотя, конечно, он не такой же как массив в Java.
А с какой стати длина массива является его частью? Wiki говорит просто, что массив — это «тип или структура данных в виде набора компонентов (элементов массива), расположенных в памяти непосредственно друг за другом». О том, где и в каком виде (есть ведь варианты массивов без длины в явном виде — заканчивающиеся специальным элементом) хранится длина, и хранится ли она где‐либо вообще ни слова.
Нормально с ним работать, вы пропустили моё замечание, что вместо длины может быть терминатор. Ещё, возможно, вы передаёте массивы фиксированной длины (и она хранится в каком‐то #define
). Или знаете какое‐то свойство массива, позволяющее в конкретном случае обойтись без длины и без терминаторов. Или решили в качестве эксперимента отлавливать SEGV при выходе за границы, таким образом определяя длину на месте. Ещё можно вспомнить, что аллокатор длину где‐то таки хранит и можно достать её оттуда, если вас не пугает привязка к аллокатору и конкретному диапозону его версий.
Я не издеваюсь. Я говорю, что поддержка массивов на уровне языка не предполагает поддержку длины массива. Эта часть ветки началась с утверждения «в Си нет массивов», основанного на «у сишных массивов нет контроля границ, длину надо таскать явно вместе с указателем». Сейчас идёт дискуссия «что считать массивом» — вы всё время утверждаете, что вам нужно таскать длину и указатель раздельно, вам отвечают, что
- В случае с массивами вида
Type array[LEN]
, длина известна на этапе компиляции в области видимости и таскать её не нужно. - Длина вообще не является частью массива, поэтому обосновывать отсутствие поддержки массивов в C отсутствием у массивов атрибута длины в том или ином виде некорректно.
#include <iostream.h>
template<typename T, size_t N>
void print_array(T(&array)[N]) {
for (auto& element : array) {
std::cout << element << std::endl;
}
}
int main() {
int array[] = {1, 2, 3};
print_array(array);
}
---
$ g++ test.cc -o test
$ ./test
1
2
3
Как видим и массивы есть и длина отлично передаётся куда нужно. Чего вам ещё для «щастя» нужно?Это C++. Дискуссия о C, здесь длина просто так никуда не передаётся. И C не является частью C++, равно как и наоборот. Когда‐то C++ можно было рассматривать как надстройку над C, но это уже давно не так.
Дискуссия о C, здесь длина просто так никуда не передаётся.Вот прямо даже так? Отлично она передаётся до тех пор, пока вы «голый» массив не попытаетесь передать в функцию. Заверните в структуру — и сможете передавать ваш массив куда угодно.
Дизайнерское решение, возможно, не самое удачное, но говорить на этом основании, что «в C нет массивов» глупо.
О том, что в C «нет массивов» говорю не я. Но с тем, что «у массивов C в большинстве случаев (исключая статические массивы) нет поддержки длины на уровне языка» я согласен — такой «завёрнутый» массив потребует от вас самому писать код для поддержки актуальности длины, это не поддержка на уровне языка.
Но с тем, что «у массивов C в большинстве случаев (исключая статические массивы) нет поддержки длины на уровне языка» я согласен — такой «завёрнутый» массив потребует от вас самому писать код для поддержки актуальности длины, это не поддержка на уровне языка.Не потребует. Все операции, которые имеются в таком высокоуровневом языке программирования как Pascal (ISO 7185:1990, не путать с Extended Pascal ISO/IEC 10206) в C имеются тоже и отлично работают для «завёрнутых в структуру» массивов. Включая передачу параметров и прочее.
Pascal, как бы, считается фактически «эталоном» языка высокого уровня, так что неясно о чём вы тут спорите. О том, что C содержит низкоуровневые операции, которые не очень «к лицу» высокоуровневому языку? Да, разумеется, кому-то это нравится, кому-то — нет. О том, что в нём нет поддержи массивов? Нет — поддержка есть и ничуть не худшая чем во многих других языках, которые родились тогда же, когда и C.
Какие операции вы имеете ввиду? Просто покажите код, в котором создаётся массив с сохранённой где‐то длиной, а потом его длина изменяется и это передаётся в функцию. Я не вижу, как вы могли бы это сделать, не реализовав либо небольшую библиотеку для работы с массивами и спрятав нужные манипуляции там, либо не трогая в явном виде переменую/атрибут структуры с длиной.
Какие операции вы имеете ввиду?Все, поддерживаемые «эталонным» языком программирования высокого уровня под названием Pascal (ISO 7185:1990), как я уже сказал. За исключением автоматической проверки выхода за границы массивов, возможно (хотя и в Паскале с ней всё непросто: на практике очень много компиляторов её не поддерживают по умолчанию… некоторые не поддерживают вообще).
Просто покажите код, в котором создаётся массив с сохранённой где‐то длиной, а потом его длина изменяется и это передаётся в функцию.Для начала вы покажите мне такой код на языке Pascal (ISO 7185:1990), а потом уж будем говорить.
Я не вижу, как вы могли бы это сделать, не реализовав либо небольшую библиотеку для работы с массивами и спрятав нужные манипуляции там, либо не трогая в явном виде переменую/атрибут структуры с длиной.А откуда такие хотелки? Вы и в Java так не сделаете!
Есть, правда, другая задача: написать одну функцию, работающую с разными массивами — ну так этого мало где было в те времена, когда C разрабатывался. Никакие распространённые языки «родом из 80х» так не умеют! Ни Pascal (ISO 7185:1990), ни Ада 83, ни, как мы уже убедились, C…
Для решения этой задачи, собственно, и появились такие языки как Extended Pascal (ISO/IEC 10206), Ada95 и C++.
Так были в языках, существовавших ранее массивы или нет? Была ли у них поддержка длины? Я думаю Вирт очень сильно удивится если вы начнёте ему объяснять, что в Pascal «нет поддержки длины массива на уровне языка» — он как бы этим и славился с момента своего создания (хотя многие коммерческие компиляторы, появившиеся позднее на эту поддержку «забили»). Но вот исполнить ваши хотелки — да, он не мог. Никак. Не было там таких механизмов.
То, что считает массивом спецификация C99 и то, что считается массивом по той же wiki — разные вещи. Я, wiki, и, наверняка, многие программисты, считают int *array = malloc(len * sizeof(*array));
массивом, но константой в этом случае длина не является. Это уже не первый пример наличия собственных терминов в спецификации: по C99 в результате операции (uint8_t)255 + (uint8_t)1
не произойдёт переполнения — полученный 0 можно скорее описать как «сложение по модулю sizeof(uint8_t)*CHAR_BITS + 1
», но спецификация в явном виде говорит, что это не переполнение. Тем не менее, большинство известных мне программистов назовут это переполнением беззнакового целого.
Во-вторых, вопрос был показать в стандарте языка тип массив, про который известна его длина — я его показал, что не так?
В-третьих, я лично считаю массивом любую непрерывную в памяти последовательность элементов одного типа, и массивы из стандарта языка C прекрасно согласуются с этим.
Массивы в выделенной malloc
памяти тоже «непрерывные в памяти последовательности элементов одного типа» (в wiki примерно такое же определение, и я исходил именно из него — но это более широкое определение, чем то, что спецификация называет массивом), но никакой длины к ним C не прилагает. Я выше отвечал вашему оппоненту, что длина не является неотъемлемой частью массива, и существование массивов в C следует рассматривать именно исходя из этого: массивы в C есть, но длина к массиву не прилагается и не должна.
Кроме того, те массивы с известной на этапе компиляции длиной, о которых вы говорите, существуют только в определённой области видимости. Хотите передать их за пределы (даже в соседнюю функцию, при условии, что массив объявлен внутри данной) — организуйте передачу длины отдельно. Нормально они передаются, только будучи частью составного типа, причём если в составном типе массив постоянной длины. Это не делает их не массивами, но это заставляет программистов использовать в качестве массивов именно указатели на область памяти, не допуская выхода за границы массива различными способами, включая передачу длины отдельно от массива.
Массивы в выделенной malloc памяти тоже «непрерывные в памяти последовательности элементов одного типа»
Я хоть где-нибудь утверждал обратное?
Это не делает их не массивами, но это заставляет программистов использовать в качестве массивов именно указатели на область памяти
Я не понимаю, что вы пытаетесь мне доказать? Давайте начнем с того, что вы укажите с каким из моих утверждений вы не согласны, и может быть тогда ваши аргументы будут иметь для меня хоть какой-то смысл?
С «даже для языка C масив и указатель — это разные типы»: по спецификации массивом называется конкретный класс типов, тогда как с точки зрения программиста int *
— часто тоже массив, но тем не менее это указатель. И массив вида Type *
пасуют между функциями (в моей практике) чаще, чем что‐то ещё.
Или, скорее, с тем, что следует рассматривать «массивы» (в терминах спецификации) C как поддержку языком массивов (соответствующих более общему определению), а не показывать вместо этого, что язык поддерживает использование Type *
как массивов — и того, что есть здесь достаточно для того, чтобы утверждать, что массивы поддерживаются.
С «даже для языка C масив и указатель — это разные типы»: по спецификации массивом называется конкретный класс типов, тогда как с точки зрения программиста int * — часто тоже массив
Во-первых, язык C определяется стандратом. В стандарте есть разделение между типом массив и указатель. Это разделение есть не случайно, а потому что это разные типы с точки зрения языка и ведут они себя по-разному. Вы хотите оспорить, что стандарт языка проводит такое разделение?
Во-вторых, я где-то утверждал, что указатель нельзя интерпретировать как массив и использовать его вместо встроенных язык массивов?
Или, скорее, с тем, что следует рассматривать «массивы» (в терминах спецификации) C как поддержку языком массивов (соответствующих более общему определению)
Такое ощущение, что вы не со мной спорите, а со своим воображением. Был задан конкретный вопрос, про тип «массив» с аттрибутом «длина» в стандарте языка, я дал конкретный ответ — типа с аттрибутом «длина» нет, но тип «массив», для которого длина это параметр типа есть. Вы оспариваете корректность ответа?
Такое ощущение, что вы не со мной спорите, а со своим воображением. Был задан конкретный вопрос, про тип «массив» с аттрибутом «длина» в стандарте языка, я дал конкретный ответ — типа с аттрибутом «длина» нет, но тип «массив», для которого длина это параметр типа есть. Вы оспариваете корректность ответа?
Возможно. Корректность ответа на этот вопрос я не оспариваю, но дискуссия началась с того, что «C низкоуровневый» и, потом, «в C нет массивов». На второе утверждение, мне кажется, мы решили ответить по‐разному, вокруг этого и спор.
Причём правильный ответ, как бы, знают обе стороны: да, массивы в C, конечно же, есть. Нет, передать их в функцию нельзя. Ограничение странное, но делает ли оно, само по себе, язык C языком низкого уровня? Можно спорить до бесконечности.
что «C низкоуровневый» и, потом, «в C нет массивов».
Так вы бы тогда и адресовали свои аргументы, тому кто высказывает такие мнения, а не мне.
На второе утверждение, мне кажется, мы решили ответить по‐разному, вокруг этого и спор.
Вам может только казаться, потому как я на такой вопрос вообще не отвечал.
Мы рассмотрим очень простую программу на С, которая суммирует 1 миллиард байтов из файла менее чем за 0,5 секунды. Попробуйте проделать это на любом из высокоуровневых языков — вы и не приблизитесь по производительности к С. Даже на Java, с помощью JIT, с параллельными вычислениями и хорошей моделью использования памяти в пространстве пользователя
Эээ, чтение из файла всегда намного медленнее, чем сложение байтов, то есть при параллельных вычислениях все будет определяется скоростью чтения из файла, а оно во всех языках сделано как правило системными вызовами ОС или низкоуровневым кодом (считай тем же C), то есть производительность будет везде одинаковая (плюс/минус незначительные расходы на обертки системных функций) и если не считать прогрев JVM (а есть способы компильнуть код Java или C# в чистый машинный код, ну и в реальном приложении запуск окружения редко имеет значение), то производительность будет одна и та же. Отсюда вся статья не имеет никакого смысла.
Во-вторых, Лисп-машины тоже существовали (и из того что я знаю были несколько более коммерчески успешными, чем аппаратные реализаций Forth-а), но назвать функциональный язык с динамической типизацией и сборкой мусора низкоуровневым у меня язык не повернется. Я бы сказал, что высокоуровневость языка определяется его возможностями, а не способом их реализации.
То что вы называете стеком — это просто значение некоторого регистра и операции, которые пользуются этим регистром как указателем и изменяют его. Например, в x86 есть инструкции push и pop, которые оперируют регистром rsp и памятью, на которую он указывет — их называют инструкциями работы со стеком. Но есть в x86 и инструкии stos/lods, которые делают практически то же самое, только используют регистр rdi как укзатель, но их почему-то не называют инструкциями работы со стеком.
Другими словами, работа процессора не завязана на понятие стека — на понятие стека завязана голова инженера, который пишет ПО под этот процессор. И в этом смысле стек вызовов в языке С (или любом другом императивном языке) ничем ни хуже.
Далее, в Форт вообще всё построено на работе со стеком (стеками точнее). И С в этом отношении имеет мало общего со стековой архитектурой.
Но если вы так хотие наставивать на своем аргументе, то давайте его применять до конца — в процессорах так же не редко встречаются инструкции для поддержки AES или CRC, это значит, что процессор завязан на понятия AES и CRC? Или то, что кто-то назвал инструкции stos/lods инструкциями работы со строками, делает процессор завязанным на понятие строки?
Вообще, я настаиваю на одном аргументе, а именно С — язык высокого уровня. Языками низкого уровня я считаю Ассемблер и, возможно, Форт. Почему возможно мы, полагаю, уже обсудили.
Кстати, говоря о типизации данных как признаке языка высокого уровня, то в стандартном Форте её нет. В отличие от С.
Более того, сама возможность создать процессор под язык будет говорить о его возможной низкоуровневости.
Существование Lisp-машины показывает, что создать железяку, которая реализует даже довольно высокоуровневый язык, гипотетически, возможно. Так что по вашей логике любой язык будет низкоуровневым.
Кстати, говоря о типизации данных как признаке языка высокого уровня, то в стандартном Форте её нет. В отличие от С.
Да, наконец-то вы привели хоть одно реальное отличие. Это правда, что многие ассемблеры безтиповые, но с другой стороны посмотрим на Java байткод, непосредственно стек и локальные переменные типа не имеют, но инструкции, которые ими оперируют разделены на типы (разные целые числа, floating point и отдельный тип для ссылок) так, чтобы перед загрузкой байткода его можно было верифицровать на корректность работы с типами — это довольно слабая, но типизация.
многие ассемблеры безтиповыеfloat, int8, int32 за типы не считают?
Есть еще вроде когда в указателях доп инфу суют, например в младшие биты при выравнивании памяти.
В известных мне ассемблерах я не знаю способа указать, что значение, которое я хочу загрузить из памяти в регистр или сохранить из регистра в память имеет какой-то тип, могу указать только размер (скорее должен) этого значения.
С другой стороны, я когда-то слышал о типизированных ассемблерах, но никогда их в глаза не видел.
Есть еще вроде когда в указателях доп инфу суют, например в младшие биты при выравнивании памяти.
Это довольно простой и понятный прием, когда в младшие биты указателя вставляют какие-то флаги (например, цвет для красно-черного дерева или признак «удаленности» узла связаного списка в каких-нибудь lock-free списках), но какое это имеет отношение к типизации?
Касаемо байткода, то я вообще не стал бы его считать языком программирования. В противном случае любой шитый код (а байткод это его новое имя применительно к Java) нужно считать таковым. Пример неудачный.
В противном случае любой шитый код (а байткод это его новое имя применительно к Java) нужно считать таковым.
Во-первых, байткод это не название шитого кода применительно к Java, еще до Java байткода был байткод для Pascal виртаульной машины (p-code), а до этого был байткод для BCPL виртуальной машины (O-code), причем оба появились до появления языка Forth (откуда, как я понимаю вы и позаимствовали термин «шитый код») — не нужно все сводить к Forth;
Во-вторых, с чего бы это байткод не язык программирования? На нем точно так же можно писать программы (как и на ассемблере). Пример очень удачный, просто он вам не нравится.
Во-вторых, я вполне достаточно знаком с процесом компиляции и идеей байткода. Тем более что не нужно быть гением чтобы разобраться в этих концепциях — этому прекрасно могут научить в универе, причем не только теорию, но и практику. А так же в этом можно разобраться самостоятельно, например, LLVM неплохое место чтобы начать. Но переход на личности, вместо разумных аргументов я трактую как отсуствие последних с вашей стороны и на этом я свами закончил.
Касаемо байткода, то я вообще не стал бы его считать языком программирования. В противном случае любой шитый код (а байткод это его новое имя применительно к Java) нужно считать таковым. Пример неудачный.
Это уже терминологический спор. Если посмотреть на гостовские определения, можно обнаружить такие понятия, как «программа на машинном языке» — это то, что мы кратко называем бинарником. Т.е. сам исполняемый файл — это программа на языке программирования «машинный код».
Также, повторюсь, я считаю, что языком низкого уровня может считаться тот язык, который является мнемоническим обозначением команд процессора — машинного когда. Это, безусловно, Ассемблеры и Форт для форт-процессоров. Выше привели ещё примеры для Лиспа и лисп-процессора (ничего конкретного про него не знаю).
Далее, сама возможность реализации конструкций языка программирования в железе (машинных кодах), возможно может означать что этот язык — язык низкого уровня. А наличие таковой будет однозначным свидетельством того что данный язык программирования — язык программирования низкого уровня.
Форт для форт-процессоров
А чем Java процессоры отличаются от форт-процессоров тогда? В отличие от форт процессоров они вполне существуют и используются.
Если бы работа процессора не была бы завязана на стек, отсутствовали бы команды по работе с ним, равно как и специальный регистр для хранения указателя.
Ложное утверждение.
Команды работы со стеком в процессорах присутствуют исключительно для быстроты работы со стеком, их можно было бы заменить на несколько команд, просто ради оптимизации добавили команду для работы со стеком. Грубо говоря вместо 8х команд загрузки регистров в память и 2х команд сложения/вычитания, есть одна команда загрузки в стек.
«Специальный регистр» можно было бы заменить на не специальный регистр, просто есть команды которые сделаны что бы автоматом с ним работать, для быстроты и удобства, то есть сделана абстракция. А абстракции нужны не процессору, а исключительно мыслящим приматам- людям.
В одноадрессных ЭВМ что нельзя было использовать стек? Просто не было одной команды.
Про форт ничего не скажу, не в курсе.
Работа процессора непосредственно завязана на понятие стека.
Нет. Стек — это абстракция. Есть кристаллы с аппаратным стеком, но это не нечто сущее.
При этом С это язык высокого уровня безо всяких оговорок.
Есть всего лишь несколько регистров для работы со стеком, сам стек же хранится в оперативной памяти (или может в теневой памяти).
Команды для сохранения/извлечения состояния ЦП можно назвать стеком?
В процессорах стэк вызовов отменили теперь? Вот так новость
в мат сопроцессоре есть стек, в x86 — отсутствует стек.
Стек вызовов служит для ускорения поддержки стека вызовов языков программирования.
Важно что сама архитектура предполагает его наличие.
если у процессора нет команд для работы со стеком, приходится сохранять стек другими командами, такой «ручной» стек это стек или нет?
Пересылка из регистра S в регистр адреса
Пересылка из регистра Х в регистр данных
Строб записи
Пересылка константы 4 в регистр-защелку
Пересылка регистра S в регистр-защелку
Сложение
Пересылка результата АЛУ в регистр S
в x86 серии вплоть до x486 не было стека в процессорах
Ой.
Основное назначение стека — хранение адресов возврата из процедур и прерываний, т.е. аппаратная поддержка рекурсивности вызовов, call, ret и iret (или их аналоги).
pop / push — оне чиста «вкусняшки».
Оддельный аппаратный стек мог быть организован, как минимум — ещё на древнем 580ИК80 ака i8080.
В процессорах стэк вызовов отменили теперь? Вот так новость…просто мне показалась эта фраза не совсем корректной, и тут понеслось.
что значит у «процессора есть стек»?в самом процессоре, как у мат процессора. И возможно как у Форт (про форт ничего не знаю)
call, ret и iretможно эмулировать, mov add sub jump. Переключение таблиц дескрипторов, страниц памяти или таблицу прерываний в зашщищеном режиме так эмулировать не стоит.
ветку стоит прекращать, всё все поняли.
в самом процессоре, как у мат процессора.
Нонафига, сэр?
И возможно как у Форт (про форт ничего не знаю)
Форт by default исходит из модели стек-машины с 64кбайт общей памяти, стеком, размещённым в ней (что естествественно) и растущим «от верхней границы вниз».
call, ret и iret
можно эмулировать,
Слово «атомарность» благородному сэру незнакомо.
И прерывание, бодренько вклинившееся в эмулируемую очередь инсрукций — распишет авторский продукт по всем испостясям.
Либо потребует кажный разик enable/disable int.
ветку стоит прекращать, всё все поняли.
Зарадибога.
А можно подробнее, при каких обстоятельствах вам понадобится атомарность инструкции call или инструкции ret?
Ой.
Допустим инструкцию прервали в середине
Инструкции не прерываются, они — атомарны by design.
произошло прерывание, если обработчик прерывания корректный, то он все вернет в состояние «как было»
фкакое «какбыло»?
Эмуляция инструкции call может выглядеть примерно так:
inc SP
mov (SP),IP
add (SP),offset __next
jmp <my_func>
__next:
(Ассемблер условный, скобочки — косвенная адресация, offset — смещение до метки.)
Прерывание между первой и второй командой — даёт нам мусор в стеке, между третьей и четвёртой — пропуск вызова функции.
Возврат из функции или прерывания — при не-атомарности тоже нарисуют цену на дрова.
Инструкции не прерываются, они — атомарны by design.
Во-первых, я написал «допустим», что бы разобраться в гипотетических проблемах ситуации, когда инструкция ret или call будет прервана.
Во-вторых, то, что инструкции не прерываются не значит, что они атомарны — это разные свойства, более того инструкции не атомарны, как вы говорите, «by design» и тому есть некоторое количество примеров.
фкакое «какбыло»?
Очевидно, имеется ввиду состояние (в основном, подразумевается состояние регистров), в котором находилось ядро процессора до вызова обработчика прерывания.
Прерывание между первой и второй командой — даёт нам мусор в стеке
и в чем проблема то? По возвращению из обработчика прерывания мы запишем на стек вместо мусора правильное значение и никаких проблем.
между третьей и четвёртой — пропуск вызова функции.
с чего бы это? Опять же когда обработчик прерывания вернет управление
мы продолжим с того места, где мы остановились, т. е. c jmp, который вызовет функцию как и требовалось.
И прерывание, бодренько вклинившееся в эмулируемую очередь инсрукций — распишет авторский продукт по всем испостясям.
Либо потребует кажный разик enable/disable int.
Строго говоря, прерывание тоже может быть реализовано по-разному, например, как регистр, проверяемый перед выборкой очередной команды.
А что, по-Вашему, значит, что у «процессора есть стек»?
В контексте разговора выше — аппаратная реализация.
Основное назначение стека — хранение адресов возврата из процедур и прерываний, т.е. аппаратная поддержка рекурсивности вызовов, call, ret и iret (или их аналоги).
Это может быть микропрограммная реализация, а не аппаратная. Со стороны машинного языка будет выглядеть идентично.
В контексте разговора выше — аппаратная реализация.
Нокак, сэр?
Сама концерция [микро]процессора предполагает некую шнягу, которая умеет вычислять и «делать программу» — но не имеет памяти.
Если внутри системы будет много памяти (несколько К, к примеру) — то с чего это всё будет называться «процессором», а не микроконтроллером (однокристалльной микро-ЭВМ на старые деньги)?
Это может быть микропрограммная реализация, а не аппаратная.
Ифчом разница при программировании процессора?
Сама концепция [микро]процессора предполагает некую шнягу, которая умеет вычислять и «делать программу» — но не имеет памяти.
Это некорректное заявление.
Если внутри системы будет много памяти (несколько К, к примеру) — то с чего это всё будет называться «процессором», а не микроконтроллером (однокристалльной микро-ЭВМ на старые деньги)?
У Itanium'а 2К только регистровой памяти. Это раз, аппаратный стек может быть микроскопическим (см. Intel 4004), это два.
Ифчом разница при программировании процессора?
При программировании в машинных кодах — нифчом. Вопрос стоял в виде «есть ли в процессоре стек» и трактовке того, что такое «в процессоре есть».
Это некорректное заявление.
Прафтаа?
Ну, ежели у Вас своё собственное определение понятия «процессор»…
У Itanium'а 2К только регистровой памяти.
У последнего?
И сколько VLIW команд исполняет одновременно?
Это раз, аппаратный стек может быть микроскопическим (см. Intel 4004), это два.
И толку от трёх уровней стека?
При программировании в машинных кодах — нифчом.
А в чём ещё может программироваться процессор? — В итоге всё всегда сводится к «машинным кодам» в той или иной форме.
А Вы часто программируете микрокод процессора?
Вопрос стоял в виде «есть ли в процессоре стек» и трактовке того, что такое «в процессоре есть».
Всю жезнь эта трактовка была одинаковой — «архитектура процессора/ЭВМ предполагает реализацию стека». Различие промежду «в памяти» или «в процессоре» — никогда не делалось.
Вот в ЕС ЭВМ, как и у её прородителя — IBM System/360 стека действительно не было.
Прафтаа?
Ну, ежели у Вас своё собственное определение понятия «процессор»…
Не паясничайте. То, что вы под «памятью» понимаете исключительно внешнее ОЗУ — ваше дело. Память, если выражаться вашим языком, это такая шняга, где хранятся данные. Регистр — это память.
У последнего?
И сколько VLIW команд исполняет одновременно?
У навскидку нагугленного. 6 или 8, не помню.
И толку от трёх уровней стека?
Ну, спросите у создателей Intel 4004, расскажете.
А в чём ещё может программироваться процессор? — В итоге всё всегда сводится к «машинным кодам» в той или иной форме.
А Вы часто программируете микрокод процессора?
Мы же рассуждаем об аппаратной/микропрограммной реализации, поэтому, соответственно, надо уточнять и этот момент.
Всю жезнь эта трактовка была одинаковой — «архитектура процессора/ЭВМ предполагает реализацию стека». Различие промежду «в памяти» или «в процессоре» — никогда не делалось.
Вопрос привычки, эпохи и так далее. Вот тут выше как раз этот вопрос и поднялся.
Вот в ЕС ЭВМ, как и у её прородителя — IBM System/360 стека действительно не было.
Смотря у какой модели.
Гм… а как быть с семейством расширений Jazelle для ARM (ЕМНИП, технологию дропнули)? Получается и Java низкоуровневая :)
Гм… а как быть с семейством расширений Jazelle для ARMОдни поддерживали не язык Java, а байткод Java. Он низкоуровневый, тут и спорить не о чем. Но если считать что все языки, которые в принципе могут компилироваться в низкоуровневое представление — низкоуровневые, то у нас вообще высокоуровневых языков не останется.
ЕМНИП, технологию дропнулиНет, там смешнее. Во всех современных процессорах она «поддерживается» — и во всех через одно место. Jazelle предполагает исполнение байткода частью процессором, частью — интерпретатором в операционке. ARMv8 требует от всех процессоров поддержку режима Jazelle, но при этом требует чтобы количество аппараратно исполняемых инструкций было равно нулю. Вот такой шедевр научно-технической мысли.
Везде, где я встречался с ассемблером (в последнее время — это видео-кодэки), делается так: берется программа на Си, компилируется в ассемблер с -O3, после чего руками делается множество научных тыков: там заменили память на регистр, сям поменяли ветки при ветвлении. Так и рождается чуть-более-оптимальный ассемблерный код.
while ((readed = read(f, buf, sizeof(buf))) > 0) { for (i=0; i < readed; i+=16) { __m128i a = _mm_load_si128((const __m128i *)(buf+i)); r = _mm_add_epi8(a, r); } memset(buf, 0, sizeof(buf)); }
А зачем мемсетить весь буфер, если можно обнулять только то, что реально нужно (а в 99.9999% и вовсе не нужно, поскольку read вернёт 1024 байта)?
while ((read = read(f, buf, sizeof(buf))) > 0) { int tail = (sizeof(buf) - read) % 16; if (tail) memset(buf + read, 0, tail); for (i=0; i < read; i+=16) { __m128i a = _mm_load_si128((const __m128i *)(buf+i)); r = _mm_add_epi8(a, r); } }
Например, Fortran, относящийся к той же «возрастной группе», что и С, в некоторых специфических задачах более производителен. Ряд специализированных языков могут быть быстрее С при решении чисто математических задач.
Можно привести примеры?
Например, на Raspberry Pi Model B+, общая память 512мб, Raspbian 8:
Компиляция пример:
$ time gcc -O3 test.c
real 0m8.620s
user 0m1.150s
sys 0m0.210s
Тесты (файл test перенёс с декстопа, SD Sandisk Ultra), buffer=1024*1024:
$ time ./a.out test
Чтение завершено, сумма равна 136901097048.000000
real 1m15.829s
user 0m30.600s
sys 0m6.430s
$ time luajit test.lua test
Result:136901097048.000000
real 1m26.749s
user 0m45.850s
sys 0m9.780s
$ time python test_numpy.py test
Result:136901097048.000000
real 1m19.225s
user 0m31.570s
sys 0m10.950s
Кто-нибудь может сравнить с чем-нибудь на MIPS.
1m15.829s против 1m26.749s, большую часть времени программа ждёт данных с SD карты.
http://luajit.org/performance.html
MIPS работает на половине домашних роутеров, кстати.
$ time th test_torch.lua test
Result:136901097048.000000
real 1m4.043s
user 0m12.100s
sys 0m10.850s
Получается лидер.
О языке С и производительности