Время выполнения: итеративность против экземпляра

#java #execution-time

#java #время выполнения

Вопрос:

Я только что наткнулся на странную вещь при написании кода на Java:

Я считываю файл в bytearray ( byte[] file_bytes ), и то, что я хочу, — это вывод шестнадцатеричного дампа (как в утилитах hexdump или xxd в Linux). В принципе, это работает (см. Код цикла for, который не закомментирован), но для файлов большего размера (> 100 КБ) требуется немного времени, чтобы просмотреть фрагменты bytearray, выполнить правильное форматирование и так далее.

Но если я поменяю код for-loop на код, который закомментирован (используя класс с тем же кодом for-loop для вычисления!), он работает очень быстро.

В чем причина такого поведения?

Codesnippet:

     [...]

    long counter = 1;
    int chunk_size = 512;
    int chunk_count = (int) Math.ceil((double) file_bytes.length / chunk_size);
    for (int i = 0; i < chunk_count; i  ) {
        byte[] chunk = Arrays.copyOfRange(file_bytes, i * chunk_size, (i   1) * chunk_size);

       // this commented two lines calculate way more faster than the for loop below, even though the calculation algorithm is the same!
       /* 
        * String test = new BytesToHexstring(chunk).getHexstring();
        * hex_string = hex_string.concat(test);
        */ 

        for (byte b : chunk) {
            if( (counter % 4) != 0 ){
                hex_string = hex_string.concat(String.format("X ", b));
            } else{
                hex_string = hex_string.concat(String.format("Xn", b)); 
            }
            counter  ;
        }
    }

    [...]
  

класс BytesToHexstring:

 class BytesToHexstring {
    private String m_hexstring;

    public BytesToHexstringTask(byte[] ba) {
        m_hexstring = "";
        m_hexstring = bytes_to_hex_string(ba);
    }

    private String bytes_to_hex_string(byte[] ba) {
        String hexstring = "";
        int counter = 1;

        // same calculation algorithm like in the codesnippet above!
        for (byte b : ba) {
            if ((counter % 4) != 0) {
                hexstring = hexstring.concat(String.format("X ", b));
            } else {
                hexstring = hexstring.concat(String.format("Xn", b));
            }
            counter  ;
        }
        return hexstring;
    }

    public String getHexstring() {
        return m_hexstring;
    }

}
  

Строка hex_string:

 00 11 22 33
44 55 66 77
88 99 AA BB
CC DD EE FF
  

Тесты:

  1. file_bytes.длина = 102400 байт = 100 Кб

    • через класс: ~ 0,7 сек.
    • без класса: ~ 5,2 сек.
  2. file_bytes.длина = 256000 байт = 250 Кб

    • через класс: ~ 1,2 сек.
    • без класса: ~ 36 секунд

Ответ №1:

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

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

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