inserte una conferencia sobre la discusión prematura es la raíz de todo mal
Dicho esto, aquí hay algunos hábitos que he adquirido para evitar una eficiencia innecesaria y, en algunos casos, hacer que mi código sea más simple y más correcto también.
Esta no es una discusión de principios generales, sino de algunas cosas a tener en cuenta para evitar la introducción de ineficiencias innecesarias en el código.
Esto probablemente debería fusionarse en la larga discusión anterior. Es de sentido común que un bucle dentro de un bucle, donde el bucle interno repite un cálculo, será más lento. Por ejemplo:
for (i = 0; i < strlen(str); i++) {
...
}
Esto llevará una cantidad de tiempo horrible si la cadena es realmente larga, porque la longitud se recalcula en cada iteración del bucle. Tenga en cuenta que GCC realmente optimiza este caso porque strlen()está marcado como una función pura.
Al ordenar un millón de enteros de 32 bits, la ordenación de burbujas sería el camino equivocado . En general, la clasificación se puede realizar en tiempo O (n * log n) (o mejor, en el caso de la clasificación por radix), por lo que, a menos que sepa que sus datos serán pequeños, busque un algoritmo que sea al menos O (n * log n).
Del mismo modo, cuando se trata de bases de datos, tenga en cuenta los índices. Si usted SELECT * FROM people WHERE age = 20, y no tiene un índice de personas (edad), requerirá una exploración secuencial O (n) en lugar de una exploración de índice O (log n) mucho más rápida.
Jerarquía aritmética de enteros
Al programar en C, tenga en cuenta que algunas operaciones aritméticas son más caras que otras. Para enteros, la jerarquía es algo como esto (menos costoso primero):
Por supuesto, el compilador cosas por lo general como optimizar n / 2para n >> 1automáticamente si usted está apuntando un ordenador convencional, pero si usted está apuntando un dispositivo embebido, que no podría obtener ese lujo.
Además, % 2y & 1tienen diferentes semánticas. La división y el módulo generalmente se redondea hacia cero, pero su implementación está definida. Bueno >>y &siempre se redondea hacia el infinito negativo, lo que (en mi opinión) tiene mucho más sentido. Por ejemplo, en mi computadora:
printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1
Por lo tanto, usa lo que tiene sentido. No pienses que estás siendo un buen chico usando % 2cuando originalmente ibas a escribir & 1.
Operaciones costosas de coma flotante
Evite operaciones pesadas de coma flotante como pow()y log()en código que realmente no las necesita, especialmente cuando se trata de enteros. Tome, por ejemplo, leer un número:
int parseInt(const char *str)
{
const char *p;
int digits;
int number;
int position;
// Count the number of digits
for (p = str; isdigit(*p); p++)
{}
digits = p - str;
// Sum the digits, multiplying them by their respective power of 10.
number = 0;
position = digits - 1;
for (p = str; isdigit(*p); p++, position--)
number += (*p - '0') * pow(10, position);
return number;
}
Este uso de pow()(y las conversiones int<-> doublenecesarias para usarlo) no solo es bastante costoso, sino que crea una oportunidad de pérdida de precisión (por cierto, el código anterior no tiene problemas de precisión). Es por eso que me estremezco cuando veo este tipo de función utilizada en un contexto no matemático.
Además, observe cómo el algoritmo "inteligente" a continuación, que se multiplica por 10 en cada iteración, es en realidad más conciso que el código anterior:
int parseInt(const char *str)
{
const char *p;
int number;
number = 0;
for (p = str; isdigit(*p); p++) {
number *= 10;
number += *p - '0';
}
return number;
}