2012-01-13 7 views
6

Estoy diseñando una API en Java para un conjunto de algoritmos numéricos que actúan en matrices de dobles (para estadísticas financieras en tiempo real). Por razones de rendimiento, la API tiene que funcionar con matrices primitivas, por lo que List<Double> y similares no son una opción.Diseño de API para funciones que actúan en matrices

Un caso de uso típico podría ser un objeto de algoritmo que toma dos matrices de entrada y devuelve una matriz de salida que contiene un resultado calculado a partir de las dos entradas.

me gustaría establecer convenciones coherentes de cómo se utilizan los parámetros de matriz en la API, en particular:

  • Debería incluir una corrección en todas las funciones que los usuarios puedan actuar sobre las partes de un conjunto más amplio por ejemplo, someFunction(double[] input, int inputOffset, int length)
  • Si una función necesita parámetros de entrada y salida, ¿la entrada o la salida son las primeras en la lista de parámetros?
  • ¿Debería la persona que llama asignar una matriz de salida y pasarla como un parámetro (que podría ser reutilizado), o la función debería crear y devolver una matriz de salida cada vez que se llama?

Los objetivos son lograr un equilibrio entre la eficiencia, la simplicidad para los usuarios de API y la coherencia tanto dentro de la API como con las convenciones establecidas.

Claramente hay muchas opciones, entonces, ¿cuál es el mejor diseño de API en general?

Respuesta

2

Así que realmente suena como tres preguntas, así que aquí están mis opiniones.

Por supuesto, esto es muy subjetivo - lo que - su experiencia puede variar:

  1. Sí. Siempre incluya la longitud de desplazamiento &. Si la mayoría de los casos de uso para una función en particular no requieren esos parámetros, , sobrecargue la función para que no se requiera la entrada & de longitud.

  2. Por esto, me gustaría seguir el estándar utilizado por arraycopy:

    arraycopy (src objeto, int srcPos, dest objeto, int destPos, int longitud)

  3. La diferencia de rendimiento que aquí se va a insignificante a menos que la persona que llama repetidamente llama a sus funciones de utilidad. Si están a solo una de las cosas, no debería haber diferencia. Si son llamados repetidamente, debería hacer que la persona que llama le envíe una matriz asignada.

2
  1. Si lo hace, proporcione una opción predeterminada también (comienza en 0, longitud total).
  2. Creo que la mayoría de los usuarios esperarán la salida 2da. Sin embargo, si puede usar varargs, eso podría cambiar su opinión.
  3. Me gusta que la persona que llama pase en la matriz de salida, pero con una opción para nulo, lo que significa que el método asignará.

Elaborando el comentario vararg, digamos que tiene un método para agregar dos matrices. Si coloca la matriz de salida arg como la 1ra arg, y las 2 matrices de entrada al final, es trivial ampliar el método para agregar N arrays.

Elaborando en el n. ° 3, permitiendo que los llamantes pasen en la matriz de salida, a veces es más eficiente. Y, incluso si la ganancia es insignificante, sus usuarios, que tratan con matrices primitivas, probablemente provengan de un C o FORTRAN de fondo, y piensen que la ganancia será grande y se quejarán si no les permite ser "eficientes". :-)

2

Suponiendo que está trabajando con arreglos lo suficientemente pequeños como para asignarlos en la pila o en Eden, la asignación es extremadamente rápida. Por lo tanto, no hay ningún problema en que las funciones asignen sus propias matrices para devolver los resultados. Hacer esto es una gran victoria para la legibilidad.

Sugeriría comenzar a hacer que sus funciones operen en arreglos completos, y presentar una opción para llamar a una función con solo un segmento de una matriz solo si descubre que es útil.

+0

Creo que está escribiendo en Java, por lo que el comentario "en la pila" no tiene sentido en ese contexto. – user949300

+1

No, HotSpot asigna objetos pequeños que no escapan en la pila. –

+0

Pero dado que estas matrices se devuelven como resultados, definitivamente escapan. – user949300

0

me gustaría utilizar List<Double> y tienen los métodos devuelven el resultado como un nuevo List:

public List<Double> someFunction(List<Double> input) 
+0

generalmente un buen consejo, pero no es factible en mi caso; por razones de rendimiento, la API tiene que funcionar con matrices primitivas – mikera

1

Lo principal del diseño de la API que expone múltiples funciones es su consistencia interna. Todo lo demás viene como un segundo distante.

La decisión de si pasa o no pares de índice/longitud depende de la forma en que se espera que se use la API. Si espera que los usuarios escriban series de llamadas a métodos que toman o colocan datos en diferentes segmentos de la misma matriz, como en System.arrayCopy, entonces necesita pares de índices/longitud. De lo contrario, es una exageración.

La entrada primero o la salida primero es su decisión, pero una vez que la realice, apéguese a ella en todos los métodos con firmas similares.

Pasar el buffer de salida es una opción razonable solo si el buffer se reutiliza en el cliente. De lo contrario, es un esfuerzo desperdiciado en crear y mantener un conjunto adicional de métodos API. Por supuesto, esta decisión está estrechamente relacionada con su elección para ir con los pares índice/longitud: si toma el índice y la longitud, también debe tomar el buffer de salida.

1

Creo que el diseño de la API es en gran medida subjetivo, y/o debe estar fuertemente influenciado por los "casos de uso" de la API. Por otro lado, los casos de uso para su API dependen por completo del código del cliente.

Habiendo dicho todo esto, personalmente, me aprovecho de la sobrecarga de métodos e ir a por la siguiente estructura:

Un método con todos los parámetros:

void someFunction(int[] input1, int[] input2, int offset, int length, int[] output)

Ésta es la función principal. Todas las demás funciones simplemente lo llaman con los parámetros adecuados.

int[] someFunction(int[] input1, int[] input2, int offset, int length)

Este llama a la primera función, pero asigna y devuelve la matriz de salida en nombre de la persona que llama.

void someFunction(int[] input1, int[] input2, int[] output)

int[] someFunction(int[] input1, int[] input2)

Tenga en cuenta que la estrategia general es hacer más corta la lista de parámetros mediante la eliminación de los parámetros opcionales ''.

En general, tiendo a evitar cambiar el comportamiento del método dependiendo de si un parámetro (como la matriz de salida) es null. Puede hacer que sea más difícil detectar errores sutiles de esa manera. De ahí mi preferencia por dos estilos de llamada diferentes: uno donde se proporciona (y se requiere) el parámetro de salida y otro donde el método devuelve su salida.

Cuestiones relacionadas