В java, эффективнее ли использовать байты или короткие вместо int и float вместо double?

Я заметил, что всегда использовал int и удваивается, независимо от того, насколько маленьким или большим должно быть число. Так в java, более эффективно использовать byte или short вместо int и float вместо double?

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

Я знаю, что у java нет беззнаковых типов, но могу ли я что-нибудь сделать, если бы знал, что число будет только положительным?

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

(Я бы предположил, что сборщик мусора имеет дело только с объектами, а не с примитивами, но все же удаляет все примитивы в оставленных объектах, верно?)

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

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

Стоит ли делать это с самого начала, когда я начинаю новый проект? (Я имею в виду, я думаю, что каждый маленький кусочек мог бы помочь, но с другой стороны, если так, то почему не кажется, что кто-то делает это)

Ответ 1

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

Краткий ответ

Да, вы не правы. В большинстве случаев это не имеет большого значения с точки зрения используемого пространства.

Не стоит пытаться оптимизировать это... если у вас нет четких доказательств необходимости оптимизации. А если вам необходимо оптимизировать использование памяти объектными полями, в частности, вам, вероятно, придется принять другие (более эффективные) меры.

Более длинный ответ

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

Есть два исключения из этого:

  • Значения long и double требуют двух примитивных 32-битных ячеек
  • массивы примитивных типов представлены в упакованном виде, так что (например) массив байтов содержит 4 байта на 32-битное слово.

Поэтому, возможно, стоит оптимизировать использование long и double... и больших массивов примитивов. Но в целом нет.

In theory, a JIT might be able to optimize this, but in practice I've never heard of a JIT that does. One impediment is that the JIT typically cannot run until after there instances of the class being compiled have been created. If the JIT optimized the memory layout, you could have two (or more) "flavors" of object of the same class ... and that would present huge difficulties.


Revisitation

Глядя на результаты теста в ответе @meriton, кажется, что использование short и byte вместо int влечет за собой снижение производительности при умножении. Действительно, если вы рассматриваете операции изолированно, штраф является значительным. (Вы не должны рассматривать их изолированно... но это другая тема.)

Я думаю, что объяснение состоит в том, что JIT, вероятно, выполняет умножения с использованием 32-битных команд умножения в каждом случае. Но в случае byte и short он выполняет дополнительные инструкции для преобразования промежуточного 32-битного значения в byte или short в каждой итерации цикла. (Теоретически это преобразование может быть выполнено один раз в конце цикла... но я сомневаюсь, что оптимизатор сможет это выяснить.)

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

Ответ 2

Это зависит от реализации JVM, а также от базового оборудования. Большинство современных аппаратных средств не будут извлекать одиночные байты из памяти (или даже из кеша первого уровня), т.е. Использование младших примитивных типов обычно не снижает потребление пропускной способности памяти. Аналогично, современный процессор имеет размер слова 64 бит. Они могут выполнять операции с меньшим количеством бит, но это работает, отбрасывая лишние биты, что также не быстрее.

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

В общем случае, однако, использование младших примитивных типов происходит не быстрее.

Чтобы продемонстрировать это, посмотрите следующий тест:

package tools.bench;

import java.math.BigDecimal;

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1; 
            } while (duration < 100000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }   

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    public static void main(String[] args) throws Exception {
        Benchmark[] benchmarks = {
            new Benchmark("int multiplication") {
                @Override int run(int iterations) throws Throwable {
                    int x = 1;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("short multiplication") {                   
                @Override int run(int iterations) throws Throwable {
                    short x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("byte multiplication") {                   
                @Override int run(int iterations) throws Throwable {
                    byte x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("int[] traversal") {                   
                @Override int run(int iterations) throws Throwable {
                    int[] x = new int[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = i;
                    }
                    return x[x[0]];
                }
            },
            new Benchmark("short[] traversal") {                   
                @Override int run(int iterations) throws Throwable {
                    short[] x = new short[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = (short) i;
                    }
                    return x[x[0]];
                }
            },
            new Benchmark("byte[] traversal") {                   
                @Override int run(int iterations) throws Throwable {
                    byte[] x = new byte[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = (byte) i;
                    }
                    return x[x[0]];
                }
            },
        };
        for (Benchmark bm : benchmarks) {
            System.out.println(bm);
        }
    }
}

который печатает на моем несколько старом ноутбуке:

int multiplication  1.530 ns
short multiplication    2.105 ns
byte multiplication 2.483 ns
int[] traversal 5.347 ns
short[] traversal   4.760 ns
byte[] traversal    2.064 ns

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

Ответ 3

Использование byte вместо int может увеличить производительность, если вы используете их в огромных количествах. Вот эксперимент:

import java.lang.management.*;

public class SpeedTest {

/** Get CPU time in nanoseconds. */
public static long getCpuTime() {
    ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    return bean.isCurrentThreadCpuTimeSupported() ? bean
            .getCurrentThreadCpuTime() : 0L;
}

public static void main(String[] args) {
    long durationTotal = 0;
    int numberOfTests=0;

    for (int j = 1; j < 51; j++) {
        long beforeTask = getCpuTime();
        // MEASURES THIS AREA------------------------------------------
        long x = 20000000;// 20 millions
        for (long i = 0; i < x; i++) {
                           TestClass s = new TestClass(); 

        }
        // MEASURES THIS AREA------------------------------------------
        long duration = getCpuTime() - beforeTask;
        System.out.println("TEST " + j + ": duration = " + duration + "ns = "
                + (int) duration / 1000000);
        durationTotal += duration;
        numberOfTests++;
    }
    double average = durationTotal/numberOfTests;
    System.out.println("-----------------------------------");
    System.out.println("Average Duration = " + average + " ns = "
            + (int)average / 1000000 +" ms (Approximately)");


}

}

Этот класс проверяет скорость создания нового TestClass. Каждый тест делает это 20 миллионов раз, и есть 50 тестов.

Вот TestClass:

 public class TestClass {
     int a1= 5;
     int a2= 5; 
     int a3= 5;
     int a4= 5; 
     int a5= 5;
     int a6= 5; 
     int a7= 5;
     int a8= 5; 
     int a9= 5;
     int a10= 5; 
     int a11= 5;
     int a12=5; 
     int a13= 5;
     int a14= 5; 
 }

Я запустил класс SpeedTest и в итоге получил это:

 Average Duration = 8.9625E8 ns = 896 ms (Approximately)

Теперь я изменяю целые числа в байтах в TestClass и запускаю его снова. Вот результат:

 Average Duration = 6.94375E8 ns = 694 ms (Approximately)

Я считаю, что этот эксперимент показывает, что если вы создаете большое количество переменных, использование байта вместо int может повысить эффективность

Ответ 4

Байт

обычно считается 8 бит. как правило, считается 16 бит.

В "чистой" среде, которая не является java, поскольку вся реализация байтов и длин, и шорты, и другие забавные вещи, как правило, скрыты от вас, байт лучше использует пространство.

Однако ваш компьютер, вероятно, не 8 бит, а, вероятно, не 16 бит. это значит, что для получения 16 или 8 бит, в частности, ему необходимо прибегнуть к "обману", который тратит время, чтобы притворяться, что он имеет доступ к тем типам, когда это необходимо.

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

Ответ 5

Одна из причин того, что short/byte/char менее производительны, заключается в отсутствии прямой поддержки этих типов данных. Под прямой поддержкой это означает, что в спецификациях JVM не упоминается ни один набор команд для этих типов данных. Инструкции типа store, load, add и т.д. имеют версии для типа данных int. Но у них нет версий для коротких/байт/символов. Например, рассмотрим ниже код Java:

void spin() {
 int i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

То же самое преобразуется в машинный код, как показано ниже.

0 iconst_0 // Push int constant 0
1 istore_1 // Store into local variable 1 (i=0)
2 goto 8 // First time through don't increment
5 iinc 1 1 // Increment local variable 1 by 1 (i++)
8 iload_1 // Push local variable 1 (i)
9 bipush 100 // Push int constant 100
11 if_icmplt 5 // Compare and loop if less than (i < 100)
14 return // Return void when done

Теперь рассмотрим изменение int на short, как показано ниже.

void sspin() {
 short i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

Соответствующий машинный код изменится следующим образом:

0 iconst_0
1 istore_1
2 goto 10
5 iload_1 // The short is treated as though an int
6 iconst_1
7 iadd
8 i2s // Truncate int to short
9 istore_1
10 iload_1
11 bipush 100
13 if_icmplt 5
16 return

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

Причина, приведенная для отказа от прямой поддержки, приводится ниже:

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

Цитируется из спецификации JVM, представленной здесь (стр. 58).

Ответ 6

Разница вряд ли заметна! Это скорее вопрос дизайна, уместности, единообразия, привычки и т.д. Иногда это просто вопрос вкуса. Когда все, о чем вы заботитесь, - это то, что ваша программа встает и работает, а подстановка float для int не навредит правильности, я не вижу никакого преимущества в том, чтобы идти за тем или иным, если вы не можете продемонстрировать, что использование любого типа изменяет производительность. Производительность настройки, основанная на типах, которые отличаются в 2 или 3 байтах, на самом деле является последней вещью, о которой вам следует заботиться; Дональд Кнут однажды сказал: "Преждевременная оптимизация - корень всего зла" (не уверен, что это был он, отредактируйте, если у вас есть ответ).