воскресенье, 23 ноября 2014 г.

asm.js vs plain c performance test

Много читал в разных местах про asm.js как утилиту, позволяющую портировать обычный код С/С++ в нечто, что можно вставить в веб-страницу и оно там будет работать без каких-либо дополнительных усилий со стороны браузера (не нужно ставить никаких плагинов, расширений и проч.), достаточно просто поддержки javascript, которая сегодня есть у всех популярных браузеров.
 
asm.js - это, по сути, обычный javascript, который активно используется в вебе. Подмножество javascript, если быть более точным. Больше ответов на другие вопросы на официальной странице и странице компилятора.

Очень захотелось лично попробовать это. Наваял небольшую программу:

#include "md5.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
 
int main()
{
    time_t t1;
    time(&t1);
    const unsigned int dataSize = 1024 * 1024 * 30;
    unsigned char * data;
    data = (unsigned char*) malloc (dataSize);
 
    srand((unsigned int) t1);
    for(int i = 0; i < dataSize; i++)
    {
        data[i] = rand() % 255;
    }
 
    MD5_CTX mdContext;
    for(int i = 0; i < 100; i++)
    {
        MD5Init(&mdContext);
        MD5Update(&mdContext, data, dataSize);
        MD5Final (&mdContext);
    }
 
    free(data);
 
    time_t t2;
    time(&t2);
 
    const double seconds = difftime(t2, t1);
    printf ("\n%3.2f seconds", seconds);
 
 
    return 0;
}

Что мы тут делаем. Выделяем кусок памяти в 30 мегабайт, заполняем его случайными числами, а потом 100 раз вычисляем хеш этого массива, освобождаем память. Сначала запустил как обычную консольную программу, а потом скомпилировал в html-файл, который открыл в chrome и firefox.

.pro-файл консольного приложения:
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt
QMAKE_CFLAGS_RELEASE += -O3 -std=c99
SOURCES += main.c


Скрипт для компилирования html-страницы:
emcc -o2  main.c  -o md5.html -s TOTAL_MEMORY=40000000 

В скрипте параметр TOTAL_MEMORY указывает сколько памяти может потребить скрипт. С этим параметром надо обращаться осторожно и не указывать слишком больше число "про запас", без необходимости.
Файл md5.h я взял здесь.

Результаты:
console app: 14 sec, 31MB RAM
firefox: 36 sec,
chrome: 58 sec, 71 MB RAM

Обращает на себя внимание, что chrome работает заметно медленнее firefox. А последний не так радикально медленнее "чистого" си - всего в 2.5 раза. Потребление памяти тоже любопытно. Консольная программа ожидаемо заняла около 31 мегабайта памяти, страница chrome заняла 71 мегабайт. Всего в 2 раза больше, чем консольная. По-моему, очень и очень неплохо. Стоит сказать большое спасибо тем, кто создал и оптимизировал emscripten до такой степени, что javascript проигрывает C в быстродействии всего лишь в 2-4 раза и в потреблении памяти в 2 раза. Это делает возможным проивзодить некоторые достаточно тяжелые вычисления прямо на веб-странице, в браузере клиента.



пятница, 21 февраля 2014 г.

Ридер для магнитных лент KDM-96XX

Ниже представлен код для карт-ридера магнитных лент KDM-96XX.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <string.h>

#define CRTSCTS  020000000000    /* flow control */


#define KDM96XX_HEADER 0x60

#define KDM96XX_CONTROL_CLA 0x43

#define KDM96XX_CONTROL_EJECT_CMD 0x31
#define KDM96XX_CONTROL_PRESPOS_CMD 0x32
#define KDM96XX_CONTROL_INIT_CMD 0x33
#define KDM96XX_CONTROL_GETVER_CMD 0x34

#define KDM96XX_MSCARD_CLA 0x4D
#define KDM96XX_MSCARD_FREAD_CMD 0x31
#define KDM96XX_MSCARD_BREAD_CMD 0x32
#define KDM96XX_MSCARD_READCLEAR_CMD 0x33

char calculate_xor(char * data, int len)
{
    char rslt = 0;
    int i;
    for(i=0; i<len; i++)
        rslt ^= data[i];
   
    return rslt;
}

int set_interface_attribs (int fd, int speed, int parity, float timeoutSec)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
            printf("error from tcgetattr");
            return -1;
        }

        cfsetospeed (&tty, speed);
        cfsetispeed (&tty, speed);

        tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
        // disable IGNBRK for mismatched speed tests; otherwise receive break
        // as \000 chars
        tty.c_iflag &= ~IGNBRK;         // ignore break signal
        tty.c_lflag = 0;                // no signaling chars, no echo,
                                        // no canonical processing
        tty.c_oflag = 0;                // no remapping, no delays
        tty.c_cc[VMIN]  = 0;            // read doesn't block
        tty.c_cc[VTIME] = (int)(timeoutSec * 10.0);// read timeout in sec

        tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

        tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
                                        // enable reading
        tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
        tty.c_cflag |= parity;
        tty.c_cflag &= ~CSTOPB;
        //tty.c_cflag &= ~CRTSCTS;

        if (tcsetattr(fd, TCSANOW, &tty) != 0)
        {
            printf("error from tcsetattr");
            return -1;
        }
        return 0;
}

void set_blocking (int fd, int should_block, float timeoutSec)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
            printf ("error from tcgetattr");
            return;
        }

        tty.c_cc[VMIN]  = should_block ? 1 : 0;
        tty.c_cc[VTIME] = (int)(timeoutSec * 10.0);  // read timeout in sec

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
            printf("error setting term attributes");
}

void show_char_vect_as_hex(char * buf, int size)
{
    printf("[");
    int i;
    for(i=0; i<size; i++)
    {
        printf("x%02x", (unsigned int) buf[i]);
        if(i != size-1)
            printf(", ");
    }
    printf("]");
}

void show_char_vect_as_dec(char * buf, int size)
{
    printf("[");
    int i;
    for(i=0; i<size; i++)
    {
        printf("%02d", (unsigned int) buf[i]);
        if(i != size-1)
            printf(", ");
    }
    printf("]");
}

void send_init_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_CONTROL_CLA;//instructions class
    data[4] = KDM96XX_CONTROL_INIT_CMD;//command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);   
    printf("\n'init', sent bytes: ");
    show_char_vect_as_hex(data, len);
    if(rslt < 0)
    {
        printf(" write error!!");
    }
}

void send_getver_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_CONTROL_CLA;//instructions class
    data[4] = KDM96XX_CONTROL_GETVER_CMD; //command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);   
    printf("\n'getver', sent bytes: ");
    show_char_vect_as_hex(data, len);
    if(rslt < 0)
    {
        printf(" write error!!");
    }
}
void send_report_the_presence_and_pos_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_CONTROL_CLA;//instructions class
    data[4] = KDM96XX_CONTROL_PRESPOS_CMD;//command
    data[5] = calculate_xor(data, len-1);   
    int rslt = write(fd, data, len);
    if(rslt < 0)
    {
        printf(" send_report_the_presence_and_pos_cmd(), write error\n");
        return;
    }
    printf("\n'presence and position', sent bytes: ");
    show_char_vect_as_hex(data, len);
}

void send_fread_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_MSCARD_CLA;//instructions class
    data[4] = KDM96XX_MSCARD_FREAD_CMD;//command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);
    if(rslt < 0)
    {
        printf("send_fread_cmd(), write error\n");
        return;
    }
    printf("\n'fread', sent bytes: ");
    show_char_vect_as_hex(data, len);
}

void send_bread_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_MSCARD_CLA;//instructions class
    data[4] = KDM96XX_MSCARD_BREAD_CMD;//command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);
    if(rslt < 0)
    {
        printf("send_bread_cmd(), write error\n");
        return;
    }
    printf("\n'bread', sent bytes: ");
    show_char_vect_as_hex(data, len);
}

void send_cread_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_MSCARD_CLA;//instructions class
    data[4] = KDM96XX_MSCARD_READCLEAR_CMD; //command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);
    if(rslt < 0)
    {
        printf("send_cread_cmd(), write error\n");
        return;
    }
    printf("\n'cread', sent bytes: ");
    show_char_vect_as_hex(data, len);
}

void read_and_show_resp(int fd)
{
    //read first 4 bytes (message header)
    int transmitted = -1;
    int headerSize = 3;
    char header[headerSize];
    memset(header, 0, headerSize);
    transmitted = read(fd, header, headerSize);
    if(transmitted < 0)
    {
        printf("read_and_show_resp(), read header error\n");
        return;
    }
    printf("\nheader: ");
    show_char_vect_as_hex(header, transmitted);
    //calculate message body size and read it
    int dataSize = ((unsigned int)header[1]) * 256 + (unsigned int) header[2] + 1; //plus 1 byte for LRC
    char data[dataSize];
    memset(data, 0, dataSize);
    transmitted = read(fd, data, dataSize);
    if(transmitted < 0)
    {
        printf("read_and_show_resp(), read message body error\n");
        return;
    }
    printf("\nbody: ");
    show_char_vect_as_hex(data, transmitted);
}

void show_help()
{
    printf("program {serial port device} {command name} [timeout in seconds] \n");
    printf("command name : fread, bread, cread, getver, init, pres \n");   
    printf("usage example: \n");
    printf("./program /dev/ttyS0 fread 2.5 \n");
}

void clear_buf(int fd)
{
    set_interface_attribs (fd, B9600, 0, 0.1);  // set speed to 9600 bps, 8n1 (no parity)
    set_blocking (fd, 0, 0.1);                // set no blocking
    int bufSize = 1024;
    char buf[bufSize];
    read(fd, buf, bufSize);
}

int main(int argc, char *argv[])
{
    float timeoutSec = 2.5;

    if(argc < 2)
    {
        printf("serial port name was omitted\n");
        show_help();
        return 0;
    }   
    if(argc < 3)
    {
        printf("command name was omitted\n");
        show_help();
        return 0;
    }   
   
    char *portname = argv[1];   // like "/dev/ttyS0"
    char *cmdStr = argv[2]; // "read", "init", "pres"
   
    if(argc >= 4) //if there is timeout in command line - set it
    {
        timeoutSec = atof(argv[3]);
    }

    int fd = open (portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0)
    {
        printf("error opening %s, retval %d\n", portname, fd);
        return -1;
    }
   
    clear_buf(fd);

    set_interface_attribs (fd, B9600, 0, timeoutSec);  // set speed to 9600 bps, 8n1 (no parity)
    set_blocking (fd, 0, timeoutSec);                // set no blocking
       
    if(strcmp("init", cmdStr) == 0)
    {
        send_init_cmd(fd);
    }
    if(strcmp("getver", cmdStr) == 0)
    {
        send_getver_cmd(fd);
    }
    if(strcmp("pres", cmdStr) == 0)
    {
        send_report_the_presence_and_pos_cmd(fd);
    }
    if(strcmp("fread", cmdStr) == 0)
    {
        send_fread_cmd(fd);
    }
    if(strcmp("bread", cmdStr) == 0)
    {
        send_bread_cmd(fd);
    }
    if(strcmp("cread", cmdStr) == 0)
    {
        send_cread_cmd(fd);
    }
   
    read_and_show_resp(fd);

    printf("\n");
   
    close(fd);

    return 0;
}

//The values for speed are B115200, B230400, B9600, B19200, B38400, B57600, B1200