Просмотр полной версии : Имеет ли смысл использовать короткие целые типы в локальных переменных и параметрах?
Spectramine
01.06.2017, 23:02
Вопрос, в общем, далек от программирования на ZX-Spectrum, и касается современных архитектур. Но, надеюсь, не будет сочтен оффтопом.
Итак, имеет ли смысл использовать короткие целые типы в переменных и параметрах? Допустим, мы знаем, что в данном случае диапазона выбранного короткого целого типа (например, shortint / signed char) нам хватает с головой. Надо ли нам использовать именно его, или выгоднее не париться, и использовать целый тип по умолчанию (32/64-битный)? Вопрос экономии памяти в данном случае неоднозначен, т.к. команды работы с 32битными значениям обычно короче, чем с 8-16 битными (по крайней мере, для x86, если я не ошибаюсь). Да и вообще он на современных архитектурах малоактуален, особенно для переменных, а не больших массивов данных. Более актуален вопрос быстродействия.
У кого какие соображения по этому поводу? Стоит ли крохоборствовать, выбирая для переменных/параметров по возможности короткие целые типы, или выгоднее и правильнее писать int[eger] и не париться везде, где только можно? Или даже так - будет ли работать программа быстрее (в общем случае), если в ней везде, где только можно, используются короткие целые типы для переменных и параметров?
Oleg N. Cher
02.06.2017, 00:18
Меня давно интересовал этот вопрос тоже. Да, ведь работа с короткими типами (например, с 16-битными) и смешивание их с более длинными включают в себя удлиннение/укорачивание, а это доп. команда и доп. накладные расходы. И при передаче параметров тоже ведь нельзя использовать только 16 бит на стеке. В общем случае, думаю, что такой параметр удлиняется до 32-битного.
И ещё один вопрос: насколько эффективна 32-битная арифметика на 64-битных архитектурах? Предпочтительно ли на них использовать 32-битную арифметику вместо 64-битной? Будет ли это быстрее/короче/эффективнее хоть каким-то образом?
Но вопросы эти не так просты, сходу я не разобрался, хотя несколько часов добросовестно гуглил. И, видимо, дизассемблировать код передачи параметров и считать такты - в общем случае неправильно. Это же не Z80, на i80x86 есть многоуровневые кэши процессора и прочее.
Хорошая книга по подобным вещам: Крис Касперски - Техника оптимизации программ. Эффективное использование памяти (http://www.torrentino.online/torrent/682849)
shurik-ua
02.06.2017, 00:31
можно конечно долго теоретизировать на эту тему - про кеш процессора и прочее, но я бы подошёл с чисто практическим подходом - в большом цикле прогнать свой алгоритм сначала с подогнанными размерами переменных, а потом с размером ширины регистров - и замерить хотя бы и секундомером )
s_kosorev
02.06.2017, 09:11
в stdint есть fast типы, которые говорят, дай мне к примеру тип, в который влезет 16 бит и он будет максимально быстро работать
по поводу 64/32, есть eabi где указатели 32 бит а данные 64 бит, но вообще компилятору ничего не мешает в 64 бит коде использовать 32bit инструкции для чтения констант, работы с памятью итд и он достаточно часто использует, но сами инструкции обработки использовать 64бит, они меньше по размеру, так как меньше забивается кеш и в итоге больше быстродействие, но это x86, как обстоят дела с ARM MIPS я хз, еще важно выравнивание по кеш линиям, MSVC очень часто вставляет NOP между инструкциями что бы инструкция не занимала 2 кеш строки
Возьмите рекурсивный алгоритм, где передаются через стек значения. Например, DE=Y.X(хватит для экрана ZX) и сравните с 16-битными значениями.
mastermind
02.06.2017, 11:38
Для арифметики типично максимальное быстродействие для нативного размера слова процессора. (т.е. 32 бита на 32-битном процессоре, 64 на 64-битном и т.п., 8/16-бит арифметика на обоих из вышеперечисленных может быть медленнее чем "нативная").
Но если речь о хранении, то чем больше размер, тем, естественно, больше нагрузка на кэш, больше доступов к памяти и т.п. Касательно хранения также: на быстродействие еще влияет выравнивание данных. (смотреть в документации по конкретному процессору)
Вот на этом сайте можно поиграться с типами в коде по-быстрому, посмотреть во что ассемблируется: https://godbolt.org/
- - - Добавлено - - -
Кое какая интересная информация о том в каких случаях и как C (gcc 64 бит) упаковывает/распаковывает однобайтные значения: https://ro-che.info/articles/2017-01-25-word8-space
tl;dr: байтовые параметры и при операциях над байтами (т.е. в случаях когда данные в регистрах) распаковывает до 64 бит (8 байт), в массивах данные хранит как есть - 1 байт на элемент.
Alex Rider
02.06.2017, 12:28
Сдается мне, компиляторы принудительно используют нативную длину целых параметров независимо от того, что ты хочешь туда передавать. Потому как кладет их на стек. Использовать меньшие типы в качестве аргументов имеет смысл только для отладки с включенным контролем выхода за границы тпов.
Spectramine
02.06.2017, 12:41
Сдается мне, компиляторы принудительно используют нативную длину целых параметров независимо от того, что ты хочешь туда передавать. Потому как кладет их на стек. Использовать меньшие типы в качестве аргументов имеет смысл только для отладки с включенным контролем выхода за границы тпов.
Не только для отладки. Иногда компилятор передает параметры через регистры, и в этом случае короткие типы параметров могут прилично увеличить быстродействие.
- - - Добавлено - - -
В итоге, насколько я понял, в общем случае проще и выгоднее в переменных и параметрах ставить стандартный целый тип данных.
Сдается мне, компиляторы принудительно используют нативную длину целых параметров независимо от того, что ты хочешь туда передавать.
не всегда кмк
Можно использовать какие хочешь. Все равно оптимизатор все оптимизирует как он считает нужным, а структуры выравниваются до длинного типа вообще еще на этапе компиляции.
Насколько знаю, компилятор delphi всё выравнивает на 32 бит, по крайней мере, адреса переменных точно выравнивает. Это можно увидеть на записях (record). Так же, можно его заставить не выравнивать записи, написав "packed record".
ИМХО.
Все зависит от архитектуры и компиолятора.
Если слово проца 32 бита, то как правило нет разницы для быстродействия - хранить 8 бит или 32. Считывание все равно будет один условный машинный цикл. Зато есть разница для занимаемого на стеке места. И существенная, если, напрмер много вложений или создается локальный массив.
В общем случае, для кроссплатформы, имеет смысл использовать минимально необходимые размеры целых типов. Особенно, если предполагается такими несовершенными компиляторами как SDCC.
Oleg N. Cher
06.06.2017, 01:29
Зато есть разница для занимаемого на стеке места.Не, именно на стеке параметр короче 32 бит будет занимать 32 бита. Исключительно из соображений эффективности хранения выравненных данных. А вот переменная такого размера в памяти будет занимать ровно присущий ей размер. И извлекаться из памяти она может менее эффективно, чем выравненная на 4 байта (насколько я помню из книги Криса Касперски).
Для массива или структуры, разумеется, лучше использовать короткие типы.
В общем случае, для кроссплатформы, имеет смысл использовать минимально необходимые размеры целых типов. Особенно, если предполагается такими несовершенными компиляторами как SDCC.Очень правильно сказано. Хотя вопрос был про x86 и ARM. И про параметры, а не про общее использование переменных.
Я вот не знаю. Допустим, мы использовали переменную короткого типа (16 бит), а компилятор для эффективности сделал её размером 32 бита. Будут ли потери (ожидаемые) при переполнении в расчётах или всё-таки нет? Т.е. будет ли такая переменная вести себя как короткая, чего нам и требуется (вдруг мы лихо срезали лишние старшие биты, понадеявшись на "короткость" переменной), или мы получим удивительные результаты, как будто бы она длинная? Если второе, то нуегонафиг. А если первое, то надо же занулить старшие биты, чтобы не было накладок. Притом после каждой арифметической или логической операции.
Spectramine
06.06.2017, 02:22
Я вот не знаю. Допустим, мы использовали переменную короткого типа (16 бит), а компилятор для эффективности сделал её размером 32 бита. Будут ли потери (ожидаемые) при переполнении в расчётах или всё-таки нет? Т.е. будет ли такая переменная вести себя как короткая, чего нам и требуется (вдруг мы лихо срезали лишние старшие биты, понадеявшись на "короткость" переменной), или мы получим удивительные результаты, как будто бы она длинная? Если второе, то нуегонафиг. А если первое, то надо же занулить старшие биты, чтобы не было накладок. Притом после каждой арифметической или логической операции.
У правильного компилятора короткая переменная ведет себя как короткая, все преобразования в длинную и обратно делаются прозрачно. На самом деле для большинства арифметических и логических операций дополнительные биты никак не влияют на переполнение коротких переменных, и занулять их не нужно. Короткая часть расширенной переменной ведет себя точно так же, как вела бы себя короткая переменная, а всё, что происходит в расширенной части, можно спокойно игнорировать.
Исключения - сдвиг вправо, умножение/деление, ну и возможно что-то ещё. На этот случай есть процессорные команды расширения коротких значений до длинных.
Компилятору для ARM пофиг - брать со стека 8 или 32 бита. По времени это одинаково.
А вот для 8биток - однозначно на стеке надо хранить 8 бит, если требуются 8 бит.
Итак, имеет ли смысл использовать короткие целые типы в переменных и параметрах? Допустим, мы знаем, что в данном случае диапазона выбранного короткого целого типа (например, shortint / signed char) нам хватает с головой. Надо ли нам использовать именно его, или выгоднее не париться, и использовать целый тип по умолчанию (32/64-битный)? Вопрос экономии памяти в данном случае неоднозначен, т.к. команды работы с 32битными значениям обычно короче, чем с 8-16 битными (по крайней мере, для x86, если я не ошибаюсь). Да и вообще он на современных архитектурах малоактуален, особенно для переменных, а не больших массивов данных. Более актуален вопрос быстродействия.
Лучше всего использовать "родную" разрядность.
Например 32 бита для x86 в режиме 386. Или 64 для современных.
У современных процессоров (x86/64) АЛУ обычных (не SSE) команд заточено под размер регистров. Действия для 64 бит одного операнда и 64 бит другого операнда выполняются одной микрокомандой. Для 32-битной арифметики у 64 битных процессоров есть соответствующие маски. Т.е. что 64 битная команда, что 32 битная - всё равно одна микрокоманда.
Но если используется 8битная арифметика, то некоторые современные процессоры впадают в ступор (пропускают такты) на примитивнейших командах типа ADD AH,AL. Дело в том, что здесь действие-то выполняется над частями одного регистра EAX (один и тот же регистр одновременно и первый операнд, и второй операнд и результат).
Но на самом деле не всё так просто. Сейчас новая парадигма программирования - принцип локальности данных.
Программисты стараются расположить рядом совместно используемые данные чтобы они поодновременнее попадали в кэш-память.
Соответственно, если уместите свои переменные в меньшее количество строк кэш-памяти, то и загружаться данные будут быстрее.
Итак резюме. Если данных много (мегабайты), то по возможности сжимайте их и используйте чуть ли не побитно. Усложнение программы будет незначительным, а эксплуатация кэша снизится многократно.
Если же данные целиком лезут в кэш-память, то извращаться не надо. Более того, и x86 пользоваться не надо - лучше переходить на SSE/AVX https://ru.wikipedia.org/wiki/AVX - там и регистров больше и в каждый можно запихнуть несколько переменных.
А вообще, компиляторы сейчас пошли шибко умные и сами могут делать SSE/AVX при указании соответствующих ключиков.
Powered by vBulletin® Version 4.2.5 Copyright © 2026 vBulletin Solutions, Inc. All rights reserved. Перевод: zCarot