Эффективность кода C - PullRequest
0 голосов
/ 03 мая 2018

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

Вот код:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main(void) {
    int i, r;
    time_t t;
    char hexadecimal[7];

    char* hex[16];
    hex[0] = "0";
    hex[1] = "1";
    hex[2] = "2";
    hex[3] = "3";
    hex[4] = "4";
    hex[5] = "5";
    hex[6] = "6";
    hex[7] = "7";
    hex[8] = "8";
    hex[9] = "9";
    hex[10] = "A";
    hex[11] = "B";
    hex[12] = "C";
    hex[13] = "D";
    hex[14] = "E";
    hex[15] = "F";  

    strcpy(hexadecimal, "#");

    srand((unsigned) time(&t));

    for (i = 0; i < 6; i++) {
        r = rand() % 16;
        strcat(hexadecimal, hex[r]);
    }   
    printf("%s\n", hexadecimal);

    return 0;
}

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

Ответы [ 2 ]

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

Да, ваш код довольно неэффективен. Это хорошая возможность узнать о том, как символы и строки работают вместе в C. Зная это, мы можем написать гораздо более сложную версию. Он будет работать намного быстрее, и в качестве бонуса может быть несколько проще писать и читать.

(Примечание: ваш массив hexadecimal был слишком мал. Нам нужно место для '#', плюс 6 шестнадцатеричных цифр, плюс завершающий '\0'. Поэтому он должен быть char hexadecimal[8].)

Мы собираемся создать строку hexadecimal, используя символы, а не строки. Таким образом, вместо hex, являющегося массивом строк, давайте получим массив символов:

char hex[16];
hex[0] = '0';
hex[1] = '1';
/* ... */

Но подождите, массив символов - это строка. Поэтому вместо этого мы можем написать:

char hex[16] = "0123456789ABCDEF";

или просто:

char hex[] = "0123456789ABCDEF";

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

И теперь, вместо использования strcat для объединения hex[r] строк в строящуюся строку, мы собираемся поместить символы hex[r] непосредственно в строящуюся строку:

for (i = 0; i < 6; i++) {
    r = rand() % 16;
    hexadecimal[i+1] = hex[r];
}

Вы заметите, что я присвоил hexadecimal[i+1], а не hexadecimal[i]. Мы должны «сдвинуть все вправо» на единицу, потому что строка уже содержит начальный '#'. Еще один способ сделать это (хотя и необычно в C) - использовать цикл на основе 1:

for (i = 1; i <= 6; i++) {
    r = rand() % 16;
    hexadecimal[i] = hex[r];
}

Кроме того, поскольку мы строим строку символ за раз, явно, мы могли бы также сделать наш ведущий символ '#' таким же образом, вернувшись назад и изменив

strcpy(hexadecimal, "#");

до

hexadecimal[0] = '#';

Есть еще одна вещь, которую мы должны сделать. Поскольку мы строили строку символ за раз, вручную, мы должны также позаботиться о нулевом завершении вручную:

hexadecimal[7] = '\0';

Но, в конце концов, это будет делать то же самое: генерировать правильно сформированную строку hexadecimal, содержащую шесть случайных шестнадцатеричных цифр:

printf("%s\n", hexadecimal);

И, прежде чем я уйду, сноска об эффективности. Я сказал: «Да, ваш код довольно неэффективен», и я заявил, что моя перезапись будет выполняться «гораздо быстрее». И хотя это правда, все относительно. Даже «неэффективная» версия будет работать намного быстрее, чем вы можете измерить, и разница между двумя версиями будет почти незаметна.

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

Ради интереса я скомпилировал и запустил обе версии на своем ноутбуке. «Неэффективная» версия занимала 0,002 с пользовательского времени, что измерялось командой «время» Unix. «Более эффективная» версия заняла ... 0,002 с. Так что, какова бы ни была разница, она меньше миллисекунды и затоплена проблемами, связанными с накладными расходами, такими как время, затраченное на запуск программы вообще.

Итак, я добавил еще один цикл в обе программы, чтобы они генерировали 1 000 000 случайных шестнадцатеричных строк. Теперь появилась разница: неэффективная версия заняла 0,404 с, а эффективная версия - 0,263 с. (Подумайте об этом: один миллион случайных строк менее чем за полсекунды. Компьютеры работают очень и очень быстро.) Если разделить, неэффективная версия заняла 0,404 ÷ 1000000 = .000000404 с за итерацию, или 404 наносекунд . С другой стороны, эффективная версия заняла всего 263 наносекунды. Так что да, эффективная версия более эффективна - но если вам не понадобится лот из этих случайных шестнадцатеричных значений, вы никогда не заметите разницу.


Еще одна сноска. Существует серьезная потенциальная проблема с линией

r = rand() % 16;

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

Самый простой способ исправить это (при условии, что он доступен для вас) - переключиться на лучшую функцию random():

r = random() % 16;

Если вы сделаете это, вам нужно использовать сопутствующую функцию srandom:

srandom((unsigned) time(&t));

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

Если random() вам недоступен, вы можете увернуться от проблемы rand, используя биты старшего разряда, например:

r = rand() / (RAND_MAX / 16 + 1);

См. Также список часто задаваемых вопросов C , вопросы 13.16 и 13.18 .

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

char hexadecimal[7]; слишком мало, чтобы содержать строку , как "#123456", для которой требуется 8 char.


Повторные вызовы на strcat(hexadecimal, hex[r]); неэффективны. Можно ожидать, что каждый strcat() будет "стоить" длину существующей строки. Чтобы зациклить n раз и strcat() длинная строка из n символа занимает O(n*n) время. Более эффективно отслеживать конец строки и добавлять туда.

Но давайте сделаем это без строки .


rand() генерирует значение int [0.. RAND_MAX]. RAND_MAX может быть как 0x7FFF или INT_MAX. * 1 028 * Пример * +1029 *

Код ищет для генерации 6 * 4 бит случайных данных. Так что звоните rand() 1 или 2 раза, а не 6.

// for rand(), uint32_t, printf(), PRIX32
#include <stdlib.h>
#include <stdint.h>
#include <stdlio.h>
#include <inttypes.h>

int main(void) {
  // srand() omitted for simplicity,  let OP add back in

  uint32_t r = rand();
  if (RAND_MAX < 0xFFFFFF) {
    r += r*RAND_MAX;  // like r = r*(RAND_MAX + 1) yet avoids RAND_MAX + 1 overflow
    r += rand();
  }

  // We only want 24-bits
  r &= 0xFFFFFF;
  // If RAND_MAX is a Mersenne Number  M=2^n-1, 
  // then `r` value is no more biased than `rand()`

  printf("#%06" PRIX32 "\n", r);
}

Примечание. Время выполнения операций ввода-вывода часто на 100 секунд больше, чем в остальной части этого кода.

...