Pregunta

Hice una pregunta sobre los tamaños de tipo C lo cual obtengo una respuesta bastante buena, pero me di cuenta de que no puedo formular la pregunta muy bien para ser útil para mi propósito.

Mi experiencia era de Computer Engineer antes de pasar a Software Engineer, así que me gustan las arquitecturas de computadora y siempre estoy pensando en hacer VM. Acabo de terminar un proyecto interesante haciendo VM en Java del cual estoy muy orgulloso. Pero hay algunos problemas legales que no puedo abrir ahora y actualmente tengo algo de tiempo libre. Así que quiero ver si puedo hacer otra VM en C (con mejor velocidad) solo por diversión y educación.

La cuestión es que no soy un programa C la última vez que escribí un problema C que no es trivia fue hace más de 10 años. Yo era Pascal, Delphi, y ahora programador de Java y PHP.

Hay varios obstáculos que puedo prever y estoy tratando de abordar uno y es acceder a la biblioteca existente (en Java, la reflexión resuelve este problema).

Planeo resolver esto teniendo un búfer de datos (similar a la pila). El cliente de mi VM puede programar para poner datos en estas pilas antes de darme un puntero a la función nativa.

int main(void) {
    // Prepare stack
    int   aStackSize = 1024*4;
    char *aStackData = malloc(aStackSize);

    // Initialise stack
    VMStack aStack;
    VMStack_Initialize(&aStack, (char *)aStackData, aStackSize);

    // Push in the parameters
    char *Params = VMStack_CurrentPointer(&aStack);
    VMStack_Push_int   (&aStack, 10  ); // Push an int
    VMStack_Push_double(&aStack, 15.3); // Push a double

    // Prepare space for the expected return
    char *Result = VMStack_CurrentPointer(&aStack);
    VMStack_Push_double(&aStack, 0.0); // Push an empty double for result

    // Execute
    void (*NativeFunction)(char*, char*) = &Plus;
    NativeFunction(Params, Result); // Call the function

    // Show the result
    double ResultValue = VMStack_Pull_double(&aStack); // Get the result
    printf("Result:  %5.2f\n", ResultValue);               // Print the result

    // Remove the previous parameters
    VMStack_Pull_double(&aStack); // Pull to clear space of the parameter
    VMStack_Pull_int   (&aStack); // Pull to clear space of the parameter

    // Just to be sure, print out the pointer and see if it is `0`
    printf("Pointer: %d\n", aStack.Pointer);

    free(aStackData);
    return EXIT_SUCCESS;
}

La inserción, extracción e invocación de la función nativa se puede activar mediante un código de byte (así es como se realizará la máquina virtual más adelante).

En aras de la integridad (para que pueda probarlo en su máquina), aquí está el código de Stack:

typedef struct {
    int  Pointer;
    int  Size;
    char *Data;
} VMStack;

inline void   VMStack_Initialize(VMStack *pStack, char *pData, int pSize) __attribute__((always_inline));
inline char   *VMStack_CurrentPointer(VMStack *pStack)                    __attribute__((always_inline));
inline void   VMStack_Push_int(VMStack *pStack, int pData)                __attribute__((always_inline));
inline void   VMStack_Push_double(VMStack *pStack, double pData)          __attribute__((always_inline));
inline int    VMStack_Pull_int(VMStack *pStack)                           __attribute__((always_inline));
inline double VMStack_Pull_double(VMStack *pStack)                        __attribute__((always_inline));

inline void VMStack_Initialize(VMStack *pStack, char *pData, int pSize) {
    pStack->Pointer = 0;
    pStack->Data    = pData;
    pStack->Size    = pSize;
}

inline char *VMStack_CurrentPointer(VMStack *pStack) {
    return (char *)(pStack->Pointer + pStack->Data);
}

inline void VMStack_Push_int(VMStack *pStack, int pData) {
    *(int *)(pStack->Data + pStack->Pointer) = pData;
    pStack->Pointer += sizeof pData; // Should check the overflow
}
inline void VMStack_Push_double(VMStack *pStack, double pData) {
    *(double *)(pStack->Data + pStack->Pointer) = pData;
    pStack->Pointer += sizeof pData; // Should check the overflow
}

inline int VMStack_Pull_int(VMStack *pStack) {
    pStack->Pointer -= sizeof(int);// Should check the underflow
    return *((int *)(pStack->Data + pStack->Pointer));
}
inline double VMStack_Pull_double(VMStack *pStack) {
    pStack->Pointer -= sizeof(double);// Should check the underflow
    return *((double *)(pStack->Data + pStack->Pointer));
}

En el lado de la función nativa, creé lo siguiente para fines de prueba:

// These two structures are there so that Plus will not need to access its parameter using
//    arithmetic-pointer operation (to reduce mistake and hopefully for better speed).
typedef struct {
    int    A;
    double B;
} Data;
typedef struct {
    double D;
} DDouble;

// Here is a helper function for displaying void PrintData(Data *pData, DDouble *pResult) { printf("%5.2f + %5.2f = %5.2f\n", pData->A*1.0, pData->B, pResult->D); }

// Some native function void Plus(char* pParams, char* pResult) { Data *D = (Data *)pParams; // Access data without arithmetic-pointer operation DDouble *DD = (DDouble *)pResult; // Same for return DD->D = D->A + D->B; PrintData(D, DD); }

Cuando se ejecuta, el código anterior devuelve:

10.00 + 15.30 = 25.30
Result:  25.30
Pointer: 0

Esto funciona bien en mi máquina (Linux x86 32bits GCC-C99). Será muy bueno si esto también funciona en otro sistema operativo / arquitectura. Pero hay AL MENOS tres emisiones relacionadas con la memoria que debemos tener en cuenta.

1). Tamaño de datos: parece que si compilo las funciones VM y nativas utilizando el mismo compilador en la misma arquitectura, los tipos de tamaño deberían ser los mismos.

2). Endianness: lo mismo con el tamaño de los datos.

3). Alineación de memoria: cuál es el problema, ya que los bytes de relleno se pueden agregar en la estructura, pero es difícil sincronizarlo cuando se prepara la pila de parámetros como (no hay forma de saber cómo se agrega el relleno, excepto la codificación rígida).

Mis preguntas son:

1). Si conozco el tamaño de los tipos, ¿hay alguna forma de modificar la función push y pull para sincronizar exactamente con el relleno de estructura? (modifique para que el compilador se encargue de él, como los problemas de Datasize y Endians).

2). Si empaco estructura por uno (usando #pragma pack(1)); (2.1) ¿La penalización de rendimiento será aceptable? y (2.2) ¿Estará en riesgo la estabilidad del programa?

3). ¿Qué tal un relleno de 2,4 u 8? ¿Cuál debería ser bueno para el sistema general de 32 o 64 bits?

4). ¿Me puede guiar a una documentación para un algoritmo de relleno exacto, digamos para GCC en x86?

5). ¿Hay una mejor manera?

NOTA: multiplataforma no es mi objetivo final, pero no puedo resistirme. Además, el rendimiento no es mi objetivo en cuanto no es tan feo. Todo esto es para divertirse y aprender.

Perdón por mi inglés y la publicación muy larga.

Gracias a todos de antemano.

¿Fue útil?

Solución

Comentarios tangenciales

Estos primeros elementos son tangenciales a las preguntas que hizo, pero ...

// Execute
void (*NativeFunction)(char*, char*) = &Plus;
NativeFunction(Params, Result); // Call the function

Creo que probablemente deberías estar usando 'void *' en lugar de 'char *' aquí. También tendría un typedef para el tipo de puntero de función:

typedef void (*Operator)(void *params, void *result);

Entonces puedes escribir:

Operator NativeFunction = Plus;

La función real también se modificaría, pero solo muy ligeramente:

void Plus(void *pParams, void *pResult)

Además, tiene un pequeño problema de nomenclatura: esta función es 'IntPlusDoubleGivesDouble ()', en lugar de una función de propósito general 'agregar cualquiera de los dos tipos'.


Respuestas directas a las preguntas

  

1). Si conozco el tamaño de los tipos, ¿hay alguna forma de modificar la función push y pull para sincronizar exactamente con el relleno de estructura? (modifique para que el compilador se encargue de él, como los problemas de Datasize y Endians).

No hay una manera fácil de hacer eso. Por ejemplo, considere:

struct Type1
{
     unsigned char byte;
     int           number;
};
struct Type2
{
     unsigned char byte;
     double        number;
};

En algunas arquitecturas (SPARC de 32 o 64 bits, por ejemplo), la estructura Tipo1 tendrá un 'número' alineado en un límite de 4 bytes, pero la estructura Tipo2 tendrá un 'número' alineado en un 8- límite de bytes (y podría tener un 'doble largo' en un límite de 16 bytes). Su estrategia de 'empujar elementos individuales' aumentaría el puntero de la pila en 1 después de presionar el valor 'byte', por lo que desearía mover el puntero de la pila en 3 o 7 antes de presionar el 'número', si el puntero de la pila aún no está correctamente alineado. Parte de la descripción de su VM serán las alineaciones requeridas para cualquier tipo dado; el código de inserción correspondiente deberá garantizar la alineación correcta antes de presionar.

  

2). Si empaco estructura por uno (usando #pragma pack (1)); (2.1) ¿La penalización de rendimiento será aceptable? y (2.2) ¿Estará en riesgo la estabilidad del programa?

En las máquinas x86 y x86_64, si empaqueta los datos, incurrirá en una penalización de rendimiento por el acceso a datos desalineados. En máquinas como SPARC o PowerPC (por mecki ), recibirá un error de bus o algo similar en su lugar: debe acceder a los datos en su alineación adecuada. Puede ahorrar algo de espacio de memoria, a un costo en rendimiento. Sería mejor garantizar el rendimiento (que aquí incluye 'realizar correctamente en lugar de fallar') al costo marginal en el espacio.

  

3). ¿Qué tal un relleno de 2,4 u 8? ¿Cuál debería ser bueno para el sistema general de 32 o 64 bits?

En SPARC, necesita rellenar un tipo básico de N bytes por un límite de N bytes. En x86, obtendrá el mejor rendimiento si hace lo mismo.

  

4). ¿Me puede guiar a una documentación para un algoritmo de relleno exacto, digamos para GCC en x86?

Debería leer el manual .

  

5). ¿Hay una mejor manera?

Tenga en cuenta que el truco 'Tipo1' con un solo carácter seguido de un tipo le da el requisito de alineación, posiblemente utilizando la macro 'offsetof ()' de <stddef.h>:

offsetof(struct Type1, number)

Bueno, no empacaría los datos en la pila; trabajaría con la alineación nativa porque está configurada para proporcionar el mejor rendimiento. El escritor del compilador no agrega ociosamente relleno a una estructura; lo pusieron allí porque funciona "mejor" para la arquitectura. Si decide que conoce mejor, puede esperar las consecuencias habituales: programas más lentos que a veces fallan y no son tan portátiles.

Tampoco estoy convencido de que escribiría el código en las funciones del operador para suponer que la pila contiene una estructura. Quitaría los valores de la pila a través del argumento Params, sabiendo cuáles eran los desplazamientos y tipos correctos. Si empujara un entero y un doble, entonces sacaría un entero y un doble (o, tal vez, en orden inverso, sacaría un doble y un int). A menos que esté planeando una VM inusual, pocas funcioness tendrá muchos argumentos.

Otros consejos

Publicación interesante y muestra que has trabajado mucho. Casi la publicación SO ideal.

No tengo respuestas listas, así que por favor tengan paciencia conmigo. Tendré que hacer algunas preguntas más: P

  

1). Si conozco el tamaño de los tipos, ¿hay alguna forma de modificar la función push y pull para sincronizar exactamente con el relleno de estructura? (modifique para que el compilador se encargue de él, como los problemas de Datasize y Endians).

¿Es esto solo desde el punto de vista del rendimiento? ¿Planea introducir punteros junto con tipos aritméticos nativos?

  

2). Si empaco estructura por uno (usando #pragma pack (1)); (2.1) ¿La penalización de rendimiento será aceptable? y (2.2) ¿Estará en riesgo la estabilidad del programa?

Esto es una cosa definida por la implementación. No es algo con lo que pueda contar en todas las plataformas.

  

3). ¿Qué tal un relleno de 2,4 u 8? ¿Cuál debería ser bueno para el sistema general de 32 o 64 bits?

El valor que coincide con el tamaño de palabra nativo debería brindarle un rendimiento óptimo.

  

4). ¿Me puede guiar a una documentación para un algoritmo de relleno exacto, digamos para GCC en x86?

No sé nada de la parte superior de mi cabeza. Pero he visto un código similar a esto se está utilizando.

Tenga en cuenta que puede especificar atributos de variables usando GCC (que también tiene algo llamado default_struct __attribute__((packed)) que desactiva el relleno).

Aquí hay algunas preguntas muy buenas, muchas de ellas se enredarán en algunos problemas de diseño importantes, pero para la mayoría de nosotros, podemos ver en qué está trabajando (dirkgently acaba de publicar mientras escribo para que pueda ver que está generando interés) podemos entender su inglés lo suficientemente bien como para que esté trabajando en algunos problemas del compilador y algunos problemas de diseño del idioma: se hace difícil resolver la pregunta, pero en eso ya está trabajando en JNI hay esperanza ...

Por un lado, trataría de alejarme de los pragmas; Mucha gente, muchísimos no estarán de acuerdo con eso. Para una discusión canónica de por qué vea la justificación de la posición del lenguaje D sobre el tema. Por otro lado, hay un puntero de 16 bits enterrado en su código.

Los problemas son casi infinitos, bien estudiados y es probable que nos entierren en la oposición y la intransigencia intramural. si puedo sugerir leer la Página de inicio de Kenneth Louden , así como el manual de arquitectura de Intel. Lo tengo, he intentado leerlo. La alineación de la estructura de datos, junto con muchos de los otros temas que plantea para su discusión, están profundamente enterrados en la compilación histórica de la ciencia y es probable que lo sorprenda quién sabe qué. (jerga o idiomática para asuntos imprevisibles de consecuencia)

Dicho esto, aquí va:

  1. Tamaños de tipo C ¿Qué tamaños de letra?
  2. Ingeniero informático antes de pasar a Ingeniero de software ¿Alguna vez estudiaste microcontroladores? Eche un vistazo a algunos de los trabajos de Don Lancaster.
  3. Pascal, Delphi, y ahora Java y PHP programador. Esos se eliminan comparativamente de la arquitectura básica básica de los procesadores, aunque muchas personas mostrarán o intentarán mostrar cómo se pueden usar para escribir rutinas poderosas y fundamentales. Sugiero mirar el analizador de descenso recursivo de David Eck para ver exactamente cómo comenzar a estudiar el asunto. Además, Kenneth Louden tiene una implementación de & Quot; Tiny & Quot; que es un compilador real Encontré algo no hace mucho tiempo que creo que se llamaba asm dot org ... había un trabajo muy avanzado y muy poderoso para estudiar allí, pero es un largo camino comenzar a escribir en ensamblador con la intención de entrar en la ciencia de compiladores. Además, la mayoría de las arquitecturas tienen diferencias que no son consistentes de un procesador a otro.
  4. acceder a la biblioteca existente

Hay muchas bibliotecas, Java tiene algunas buenas. No sé sobre los demás. Un enfoque es intentar escribir una lib. Java tiene una buena base y deja espacio para que a la gente le guste intentar encontrar algo mejor. Comience mejorando Knuth-Morris-Pratt o algo así: simplemente no hay escasez de lugares para comenzar. Pruebe Directorio de algoritmos de programación de computadoras y, con seguridad, mire Diccionario de algoritmos y estructuras de datos en NIST

  1. always_inline

No necesariamente, vea Dov Bulka: el trabajador tiene un Doctorado en CS y también es un autor competente en áreas donde la eficiencia de tiempo / confiabilidad-robustez, etc., no están sujetas a algunos de los negocios & modelo " paradigma de donde obtenemos algunos de los " ¡Oh! eso no importa " sobre cuestiones que realmente importan.

Como nota final, la instrumentación y el control comprenden más del 60% del mercado real de habilidades de programación logradas como usted describe. Por alguna razón, escuchamos sobre todo sobre el modelo de negocio. Permíteme compartir contigo y dentro de tidbit que tengo de una fuente confiable. Desde 10% a 60% o más el riesgo real de seguridad y propiedad proviene de problemas vehiculares que de robo, robo y ese tipo deEn g. ¡Nunca escuchará apelaciones por & "; 90 días de minerales en la instalación de extracción de minerales del condado! &"; para multas de tránsito, de hecho, la mayoría de las personas ni siquiera se dan cuenta de que las citaciones de tránsito son delitos menores de clase 4 (N.A. - EE. UU.) y en realidad son clasificables como tales.

Me parece que has dado un buen paso hacia un buen trabajo, ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top