Почему плохо использовать короткие - PullRequest
0 голосов
/ 28 мая 2018

Очень часто даже в скрипте, где у разработчика есть гарантии, что переменная никогда не будет превышать один байт, а иногда и два байта;Многие люди решают использовать int типы для каждой возможной переменной, используемой для представления чисел в диапазоне 0-1.

Почему так больно использовать char или short вместо этого?

Мне кажется, я слышал, как кто-то говорит, что int - это «более стандартный» тип .. типа.Что это значит.Мой вопрос заключается в том, имеет ли тип данных int какие-либо определенные преимущества по сравнению с short (или другими менее значимыми типами данных), из-за которых люди почти всегда прибегали к int?

Ответы [ 3 ]

0 голосов
/ 28 мая 2018

Я скептически относился к утверждению о том, что короткий код должен быть медленнее и крупнее каким-либо существенным образом (при условии, что здесь есть локальные переменные, нет споров о больших массивах, где short определенно окупаются, если это уместно), поэтому я попыталсячтобы сравнить его с моей Intel(R) Core(TM) i5 CPU M 430 @ 2.27GHz

, которую я использовал (long.c):

long long_f(long A, long B)
{
    //made up func w/ a couple of integer ops 
    //to offset func-call overhead
    long r=0;
    for(long i=0;i<10;i++){
        A=3*A*A;
        B=4*B*B*B;
        r=A+B;
    }
    return r;
}

в версиях на long, int и short (%s/long/TYPE/g), построил программу с gcc и clang в -O3 и -Os и измерил размеры и время выполнения для 100-минутных вызовов каждой из этих функций.

чч:

#pragma once
int int_f(int A, int B);
short short_f(short A, short B);
long long_f(long A, long B);

main.c:

#include "f.h"
#include <stdlib.h>
#include <stdio.h>
#define CNT 100000000
int main(int C, char **V)
{
    int choose = atoi(V[1]?:"0");
    switch(choose){
    case 0:
        puts("short");
        for(int i=0; i<CNT;i++)
            short_f(1,2);
        break;
    case 1:
        puts("int");
        for(int i=0; i<CNT;i++)
            int_f(1,2);
        break;
    default:
        puts("long");
        for(int i=0; i<CNT;i++)
            long_f(1,2);
    }
}

сборка:

#!/bin/sh -eu
time(){ command time -o /dev/stdout "$@"; }
for cc in gcc clang; do
    $cc -Os short.c -c
    $cc -Os int.c -c
    $cc -Os long.c -c
    size short.o int.o long.o
    $cc main.c short.o int.o long.o

    echo $cc -Os
    time ./a.out 2
    time ./a.out 1
    time ./a.out 0

    $cc -O3 short.c -c
    $cc -O3 int.c -c
    $cc -O3 long.c -c
    size short.o int.o long.o
    $cc main.c short.o int.o long.o
    echo $cc -O3
    time ./a.out 2
    time ./a.out 1
    time ./a.out 0
done

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

   text    data     bss     dec     hex filename
     79       0       0      79      4f short.o
     80       0       0      80      50 int.o
     87       0       0      87      57 long.o
gcc -Os
long
3.85user 0.00system 0:03.85elapsed 99%CPU (0avgtext+0avgdata 1272maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps
int
4.78user 0.00system 0:04.78elapsed 99%CPU (0avgtext+0avgdata 1220maxresident)k
0inputs+0outputs (0major+74minor)pagefaults 0swaps
short
3.36user 0.00system 0:03.36elapsed 99%CPU (0avgtext+0avgdata 1328maxresident)k
0inputs+0outputs (0major+74minor)pagefaults 0swaps
   text    data     bss     dec     hex filename
    137       0       0     137      89 short.o
    109       0       0     109      6d int.o
    292       0       0     292     124 long.o
gcc -O3
long
3.90user 0.00system 0:03.90elapsed 99%CPU (0avgtext+0avgdata 1220maxresident)k
0inputs+0outputs (0major+74minor)pagefaults 0swaps
int
1.22user 0.00system 0:01.22elapsed 99%CPU (0avgtext+0avgdata 1260maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps
short
1.62user 0.00system 0:01.62elapsed 99%CPU (0avgtext+0avgdata 1228maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps
   text    data     bss     dec     hex filename
     83       0       0      83      53 short.o
     79       0       0      79      4f int.o
     88       0       0      88      58 long.o
clang -Os
long
3.33user 0.00system 0:03.33elapsed 99%CPU (0avgtext+0avgdata 1316maxresident)k
0inputs+0outputs (0major+71minor)pagefaults 0swaps
int
3.02user 0.00system 0:03.03elapsed 99%CPU (0avgtext+0avgdata 1316maxresident)k
0inputs+0outputs (0major+71minor)pagefaults 0swaps
short
5.27user 0.00system 0:05.28elapsed 99%CPU (0avgtext+0avgdata 1236maxresident)k
0inputs+0outputs (0major+69minor)pagefaults 0swaps
   text    data     bss     dec     hex filename
    110       0       0     110      6e short.o
    219       0       0     219      db int.o
    279       0       0     279     117 long.o
clang -O3
long
3.57user 0.00system 0:03.57elapsed 99%CPU (0avgtext+0avgdata 1228maxresident)k
0inputs+0outputs (0major+69minor)pagefaults 0swaps
int
2.86user 0.00system 0:02.87elapsed 99%CPU (0avgtext+0avgdata 1228maxresident)k
0inputs+0outputs (0major+68minor)pagefaults 0swaps
short
1.38user 0.00system 0:01.38elapsed 99%CPU (0avgtext+0avgdata 1204maxresident)k
0inputs+0outputs (0major+70minor)pagefaults 0swaps

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

Мой вывод заключается в том, что выбор между int и short s в теле функции или сигнатуре (массивы - другая проблема), потому что один должен работать лучше, чем другой, или генерировать более плотный код, в основном бесполезный (по крайней мере, в коде, который не привязан к конкретному компилятору с конкретными настройками).Либо быстрая, поэтому я бы выбрал тот тип, который лучше соответствует семантике моей программы, или лучше передает мой API (если я ожидаю короткое положительное значение, с тем же успехом можно использовать uchar или ushort в сигнатуре).

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

0 голосов
/ 28 мая 2018

Здесь есть несколько проблем.

  • Прежде всего, тип char совершенно не подходит для хранения целочисленных значений.Он должен использоваться только для удержания персонажей.Это связано с тем, что у него есть подпись, определяемая реализацией, char на самом деле отличается от типа signed char и unsigned char.См. Является ли символ подписанным или неподписанным по умолчанию? .

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

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

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

  • Стандартные «примитивные типы данных» C, включая char,short, int, вообще непереносимы.Они могут изменяться в размере при переносе кода, что, в свою очередь, дает им неопределенное поведение.Кроме того, C позволяет использовать всевозможные форматы непонятной и экзотической подписи для этих типов, такие как дополнение, знак и величина, биты заполнения и т. Д.

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

  • Использование меньших целочисленных типов для экономии места не является хорошей идеей по всем вышеупомянутым причинам.Опять же, stdint.h предпочтительнее.Если вам нужен универсальный тип, который переносит память, если экономия памяти не означает уменьшение скорости выполнения, используйте int_fast8_t и аналогичные.Они будут 8 битами, если использование большего типа не означает более быстрое выполнение.

0 голосов
/ 28 мая 2018

Как правило, большинство арифметических операций в C выполняется с использованием типа int (то есть обычного int, а не short или long).Это потому, что (а) определение C говорит так, что связано с тем фактом, что (b) именно так предпочитают работать многие процессоры (по крайней мере, те, о которых думали дизайнеры C).

Поэтому, если вы попытаетесь «сэкономить место», используя вместо этого short ints, и напишите что-то вроде

short a = 1, b = 2;
short c = a + b;

, компилятор должен выдать код для преобразования a из short в int, преобразовать b из short в int, выполнить сложение и преобразовать сумму обратно в short.Возможно, вы сэкономили немного места в хранилище для a, b и c, но ваш код, вероятно, будет больше (и медленнее).

Если вы вместо этого напишите

int a = 1, b = 2;
int c = a + b;

вы тратите немного больше места на a, b и c, но код, вероятно, меньше и быстрее.

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

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

См. также этот предыдущий вопрос SO и эту запись C C FAQ .


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


Приложение 2. Вот точка данных.Я экспериментировал с кодом

extern short a, b, c;

void f()
{
    c = a + b;
}

, который я скомпилировал с двумя компиляторами, gcc и clang (компиляция для процессора Intel на Mac).Затем я изменил short на int и снова скомпилировал.int -используемый код был на 7 байтов меньше при gcc и на 10 байтов меньше при clang.Проверка вывода на ассемблере позволяет предположить, что разница заключалась в урезании результата для его сохранения в c;выборка short в отличие от int, по-видимому, не меняет счетчика команд.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...