style guide c++ c performance code-readability input-parameters

c++ - guide - Sobre el orden de los parámetros de entrada



c++ naming conventions (4)

De alguna manera he encontrado algunas páginas relacionadas.

https://softwareengineering.stackexchange.com/questions/101346/what-is-best-practice-on-ordering-parameters-in-a-function

https://google.github.io/styleguide/cppguide.html#Function_Parameter_Ordering

Por lo tanto, el primer estilo de C ++ de Google no responde realmente a la pregunta, ya que no responde al orden real dentro de los parámetros de entrada o de salida.

La otra página básicamente sugiere que los parámetros de orden en un sentido son fáciles de entender y usar.

En aras de la legibilidad, personalmente prefiero ordenar los parámetros según el orden del alfabeto. Pero también puede trabajar en alguna estrategia para nombrar los parámetros para que estén bien ordenados de modo que aún puedan ser fáciles de entender y usar.

Para una función / método que contiene muchos parámetros de entrada, ¿hace una diferencia si pasa en diferentes órdenes? Si lo hace, ¿en qué aspectos (legibilidad, eficiencia, ...)? Tengo más curiosidad acerca de cómo debo hacer para mis propias funciones / métodos?

Me parece que:

  1. Los parámetros que pasan por referencias / punteros a menudo vienen antes que los parámetros que pasan por valores. Por ejemplo:

    void* memset( void* dest, int ch, std::size_t count );

  2. Los parámetros de destino a menudo vienen antes que los parámetros de origen. Por ejemplo:

    void* memcpy( void* dest, const void* src, std::size_t count );

  3. Excepto por algunas restricciones difíciles, es decir, los parámetros con valores predeterminados deben aparecer en último lugar. Por ejemplo:

    size_type find( const basic_string& str, size_type pos = 0 ) const;

  4. Son equivalentes funcionales (alcanzan el mismo objetivo) sin importar el orden en el que pasen.


Hablando estrictamente, no importa: los parámetros se insertan en la pila y la función que accede a ellos al sacarlos de la pila de alguna manera.

Sin embargo, la mayoría de los compiladores de C / C ++ le permiten especificar convenciones de llamada alternativas. Por ejemplo, Visual C ++ es compatible con la convención __fastcall que almacena los primeros 2 parámetros en los registros ECX y EDX, que (en teoría) deberían proporcionarle una mejora de rendimiento en las circunstancias adecuadas.

También hay __thiscall que almacena this puntero en el registro ECX. Si estás haciendo C ++, esto puede ser útil.


Hay algunas razones por las que puede importar, que se enumeran a continuación. El estándar de C ++ en sí no exige ningún comportamiento en particular en este espacio, por lo que no hay una manera portátil de razonar sobre el impacto en el rendimiento, e incluso si algo es demostrablemente (un poco) más rápido en un ejecutable, un cambio en cualquier parte del programa o en el compilador opciones o versión, puede eliminar o incluso revertir el beneficio anterior. En la práctica, es extremadamente raro escuchar a las personas hablar sobre el orden de los parámetros, lo que tiene alguna importancia en su ajuste de rendimiento. Si realmente le importa, es mejor que examine la salida de su propio compilador y / o el código resultante de referencia.

Excepciones

El orden de evaluación de las expresiones pasadas a los parámetros de la función no está especificado, y es muy posible que pueda verse afectado por los cambios en el orden en que aparecen en el código fuente, con algunas combinaciones que funcionan mejor en el proceso de ejecución de la CPU, o generando una excepción anterior que cortocircuita alguna otra preparación de parámetros. Este podría ser un factor de rendimiento significativo si algunos de los parámetros son objetos temporales (por ejemplo, resultados de expresiones) que son costosos de asignar / construir y destruir / desasignar. Nuevamente, cualquier cambio en el programa podría eliminar o revertir un beneficio o una penalización observada anteriormente, por lo que si le importa esto, debe crear un temporal con nombre para los parámetros que desea evaluar antes de realizar la llamada a la función.

Registros vs caché (pila de memoria)

Algunos parámetros pueden pasarse a los registros, mientras que otros se insertan en la pila, lo que efectivamente significa ingresar al menos el más rápido de los cachés de la CPU, e implica que su manejo puede ser más lento.

Si la función termina accediendo a todos los parámetros de todos modos, y la elección es entre poner el parámetro X en un registro y la Y en la pila o viceversa, no importa mucho cómo se pasan, pero dada la función puede tener condiciones. afectando a las variables que realmente se utilizan (si se trata de declaraciones, modificadores, bucles que pueden o no pueden ingresarse, devoluciones anticipadas o interrupciones, etc.), es potencialmente más rápido si una variable que realmente no se necesitaba estaba en la pila, mientras que la que se necesitaba estaba en un registro

Consulte http://en.wikipedia.org/wiki/X86_calling_conventions para obtener algunos antecedentes e información sobre las convenciones de llamadas.

Alineación y relleno.

Teóricamente, el rendimiento podría verse afectado por los minutos de las convenciones de paso de parámetros: los parámetros pueden necesitar una alineación particular para cualquier acceso a la pila, o quizás simplemente a la velocidad máxima, y ​​el compilador podría optar por rellenar en lugar de reordenar los valores que empuja, es Es difícil imaginar que sea significativo a menos que los datos para los parámetros estuvieran en la escala de tamaños de página de caché

Factores de incumplimiento

Algunos de los otros factores que mencionas pueden ser muy importantes, por ejemplo, tiendo a poner primero cualquier puntero y referencia no constantes, y nombro a la función load_xxx, así que tengo una expectativa consistente de qué parámetros pueden modificarse y en qué orden pasarlos. Sin embargo, no hay una convención particularmente dominante.


Hay algunas respuestas aquí que mencionan las convenciones de llamadas. No tienen nada que ver con tu pregunta: no importa qué convención de llamadas uses, el orden en el que declaras los parámetros no importa. No importa qué parámetros pasan los registros y cuáles se pasan por la pila, siempre que los registros pasen la misma cantidad de parámetros y la misma cantidad de parámetros se pasen por la pila. Tenga en cuenta que los parámetros que son más altos que el tamaño de la arquitectura nativa (4 bytes para 32 bits y 8 bytes para 64 bits) se pasan por una dirección, por lo que se pasan con la misma velocidad que los datos de menor tamaño .

Tomemos un ejemplo:

Tienes una función con 6 parámetros. Y tiene una convención de llamada, llamémosla CA, que pasa un parámetro por registro y el resto (5 en este caso) por pila, y una segunda convención de llamada, llamémoslo CB, que pasa 4 parámetros por registros y el resto (en este caso 2) por pila.

Ahora, por supuesto, CA será más rápido que CB, pero no tiene nada que ver con el orden en que se declaran los parámetros. Para CA, será tan rápido sin importar qué parámetro declare primero (por registro) y como declare 2º, 3º ... 6º (pila), y para CB será tan rápido independientemente de los 4 argumentos que declare para los registros y que declaras como últimos 2 parámetros.

Ahora, con respecto a tu pregunta:

La única regla que es obligatoria es que los parámetros opcionales deben ser declarados últimos. Ningún parámetro no opcional puede seguir un parámetro opcional.

Aparte de eso, puede usar el orden que desee y el único consejo que le puedo dar es ser coherente . Elige un modelo y apégate a él.

Algunas pautas que podrías considerar:

  • El destino viene antes que la fuente. Esto es para estar cerca de destination = source .
  • el tamaño del búfer viene después del búfer: f(char * s, unsigned size)
  • primero los parámetros de entrada, los parámetros de salida últimos (esto entra en conflicto con el primero que te di)

Pero no existe una directriz aceptada por el orden de los parámetros que sea "incorrecta" o "correcta" o incluso universal. Elige algo y sé consistente.

Editar

Pensé en una forma "incorrecta" de ordenar tus parámetros: por orden alfabético :).

Editar 2

Por ejemplo, tanto para CA, si paso un vector (100) como un int, será mejor que el vector (100) aparezca primero, es decir, utilice registros para cargar tipos de datos más grandes. ¿Derecha?

No. Como he mencionado, no importa el tamaño de los datos. Hablemos sobre una arquitectura de 32 bits (la misma discusión es válida para cualquier arquitectura de 16 bits, 64 bits, etc.). Analicemos los 3 casos que podemos tener con respecto al tamaño de los parámetros en relación con el tamaño nativo de la arquitectura.

  • Mismo tamaño: parámetros de 4 bytes. No hay nada que hablar aquí.
  • Tamaño más pequeño: se utilizará un registro de 4 bytes o se asignarán 4 bytes en la pila. Así que nada interesante aquí tampoco.
  • Tamaño más grande: (por ejemplo, una estructura con muchos campos, o una matriz estática). No importa qué método se elija para pasar este argumento, estos datos residen en la memoria, y lo que se pasa es un puntero (tamaño 4 bytes) a esos datos. Nuevamente tenemos un registro de 4 bytes o 4 bytes en la pila.

No importa el tamaño de los parámetros.

Editar 3

Cómo explicó @TonyD, el orden importa si no accede a todos los parámetros. Vea su respuesta.