среда, 21 августа 2013 г.

Понятие виджетов GNOME/GTK+

Попробуем коротко описать понятие виджета GTK. При изучении программирования на GTK необходимо понимать, что такое "программирование на Си в объектно-ориентированной манере". Речь идет не о объектном Си (языке программирования Objective C), а о об написании программ на "обычном" языке Си, но держа при этом написании в уме основные концепции ООП.

Объектно-ориентированный Си

  • Объектно-ориентированное программирование - это всего лишь метод организации данных и кодов. Библиотеки GNOME и GTK+ написаны на С, и их API состоит только из вызовов функций С. Тем не менее, код написан в объектно-ориентироанном стиле. Объектная ориентация не является частью языка С и ее пришлось имитировать. Имеется несколько характеристик объектов, относящихся к GNOME и GTK+, самыми важными из которых следует считать инкапсуляцию, наследование и методы.
  • Доступ к инкапсулированной информации может быть получен только посредством обращений к функциям, специально разработанным для этих целей. Эти функции называются методами.
  • Код, описывающий объект, называтся классом. Класс, из которого образуется другой класс, называется базовым. Все классы изначально происходят из GtkObject. Каждая структура включает в виде первого элемента полную структуру своего базового класса.
  • Для создания нового объекта используется метод. Все создающие объект функции имеют в своем имени слово new. Обычно функции, создающие  какой-либо виджет (кнопку, чекбокс и т.п.), возвращает указатель на структуру GtkWidget, а не структуру кнопки или чекбокса. Это возможно благодаря полиморфизму. Полиморфизм означает, что любой объект может рассматриваться в качестве одного из своих базовых классов.
  • Когда вызывается метод для работы с существующим объектом, адрес этого объекта передается как первый аргумент.
  • Методы всех классов в иерархии вызываются при использовании специальных макросов приведения типов данных. Для каждого виджета в GNOME и GTK+ имеется макрос приведения типов данных. Эти макросы делают несколько больше, чем просто приведение типов: во время работы приложения удостоверяют правильность приведения типов.
При программировании на  GTK важно понятие о событиях и их диспетчеризации.

 Последовательность диспетчеризации событий.

  • Происходит событие.
  • Соответствующая информация отправляутся менеджеру окон.
  • Менеджер окон имеет адрес очереди событий для каждого окна. Он преобразовывает полученные данные в стандартный для Х формат и ставит их в очередь, связанную с окном в которое послано событие.
  • Главный цикл приложения  использует низкоуровневую функцию системы Х для чтения событий из очереди. По идентификатору окна, присланному вместе с сообщением, программа определяет, какой из виджетов должен получить сигнал. Затем однп из функций GTK+ преобразует информацию в сигнал и передает его виджету.
  • Для каждого виджета существует функция, которая получает все поступающие сигналы.
  • Виджет просматривает список функций обратного вызова, которые связаны с определенным сигналом, и вызывает все эти функции.
Для создания сигнала можно применять функции g_signal_emit() g_signal_emit_by_name(). Соответствующие функции с префиксом gtk_ вместо g_ устарели и не рекомендуются для применения в "свежем" коде.Каждая функция обратного вызова имеет два общих параметра, но многие из функций имеют еще дополнительные параметры. Прототип самой простой функции обратного вызова выглядит так: 
void cb(GtkWidget*, gpointer); 
Если имеются дополнительные параметры, то они вставляются между двумя параметрами этого прототипа. Также кроме пустого типа могут возвращаться типы gint и boolean. На самом нижнем уровне создание события - это создание события Xevent. Xevent содержит тег, идентифицирующий окно, для которого это событие предназначено. Каждому событию соответствует своя структура. Каждая структура определена в виде двух частей. Первая - это собственно сама структура, а вторая - оператор typedef для этой структуры. Имена различаются только первым символом: имя структуры начинается символом подчеркивания.GdkEvent - это объединение нескольких структур событий. Чтобы облегчить задачу определения типа события и соответствующей ей структуры, первым полем во всех структурах является поле type. Для доступа к данным события напрямую можно выполнить приведение типа объединения к типу структуры. Какое именно приведение надовыполнить определяем из поля type.Информация о каждом событии содержится в структуре GSignalQuery. Ее поля заполняются с помощью функции g_signal_query().
typedef struct {
    guint signal_id;
    const gchar *signal_name;
    GType itype;
    GSignalFlags signal_flags;
    GType return_type; /* mangled with G_SIGNAL_TYPE_STATIC_SCOPE flag */
    guint n_params;
    const GType *param_types; /* mangled with G_SIGNAL_TYPE_STATIC_SCOPE flag */
} GSignalQuery;
У каждого виджета есть внутренние установки, которые прикладная программа может читать или записывать.Приложению необходим интерфейс для обращения к этим установкам (аргументам)  внутри указанного виджета. То есть, чтобы прочитать или записать одно из этих значений, программа должна знать имя виджета, имя переменной и тип данных этой переменной. Итак, каждый аргумент имеет тип данных и права чтения/записи. Большинство аргументов имеют права и чтения и записи.Есть два способа установить значение аргументв внутри виджета.Есть фунции, закрепленные к определенному аргументу, который можно изменять с помощью этой функции. Соответственно есть фукция для чтения этого аргумента.Второй способ заключается в использовании структуры GtkArg, у которой три поля: GtkType - определяет тип данных, указательна строку, определяющую имя аргумента, и объединение различных типов данных.Когда виджет рождается и проходит свой путь от шаблона бит в памяти до графического объекта на экране, то происходят процессы создания, реализации, преобразования и отображения. При уничтожении виджета происходят: скрывание, уничтожение соответствия, деактивизация и разрушения. Виджет может скрываться и появляться вновь. При перемещении из одного окна в другое происходят уничтожение реализации, а потом реализация.Указанным этапам соответствуют такие функции:
*_new()
gtk_widget_destroy()
gtk_widget_unref()
gtk_widget_show()
gtk_widget_hide()
gtk_widget_map()
gtk_widget_unmap()
gtk_widget_realize()
gtk_widget_unrealize()
У виджетов есть также до пяти так называемых стилей (состояний):
GTK_STATE_NORMAL
GTK_STATE_ACTIVE
GTK_STATE_PRELIGHT
GTK_STATE_SELECTED
GTK_STATE_INSENSITIVE
Для первого "философского" понимания, что такое виджет GTK - этого достаточно.

четверг, 8 августа 2013 г.

Строковые типы в Windows

В Windows существует возможность писать код, который будет откомпилирован и корректно работать со строками или в ANSI представлении или юникоде, в зависимости от значения переменной _UNICODE. Рассмотрим это на примере.
char* string1 = "ANSI string";// не зависит от значения _UNICODE
wchar_t* string2 = L"UNICODE string";// не зависит от значения _UNICODE
LPTSTR string3=_T("or ANSI or UNICODE string"); // зависит от значения _UNICODE
LPTSTR string4=TEXT("or ANSI or UNICODE string"); // то же самое
LPTSTR -это макрос, который во время работы препроцессора раскрывается либо в wchar_t* либо в char*, в зависимости от _UNICODE. То же самое относится к макросам _T и TEXT - они раскрываются в L либо в "ничего".
Приведем таблицу соответсвия некоторых таких макросов и типов, а также наиболее типичных функций, работающих со строками. Имена типов комбинируются из достаточно простых и понятных составляющих:
LP означает long pointer,
C - constant,
STR - string,
WSTR - wide character string
, но все равно можно легко запутаться, поэтому эта табличка будет полезна:
В первой колонке написаны макросы которые раскрываются в значения во второй колонке, если мы работаем с ANSI строками или в значения в третей колонке, если мы работаем с юникодом. Вобщем, если мы хотим писать "универсальный" код, нам надо вместо функций или типов пользоваться соответствующими макросами.
 Более обильная информация по подобной теме представлена в MSDN в разделе Generic-Text Routine Mappings.

вторник, 30 апреля 2013 г.

Минимальный sudoers

Приходится пользоваться утилитой sudo, достаточно редко, чтобы забыть как правильно писать правила в файл sudoers, но достаточно часто, чтобы пожалеть время, растрачиваемое на ковыряние довольно сложного мануала по данной теме. Это команда, которая необходима каждому, кто пользуется юникс.
Итак - перед нами "коза" файла /etc/sudoers:
User_Alias USER1 = myName
Host_Alias LOCALHOST = myHost
Cmnd_Alias COMMAND1 = /path/to/my/programm.exe
USER1 LOCALHOST = NOPASSWD: COMMAND1
myName - наш логин в систему;
myHost - имя нашего компьютера;
Теперь мы можем выполнять нашу программу без ввода пароля:
#sudo /path/to/my/programm.exe
По аналогии можно добавить пользователя USER2, USER3 и т.д. или команду COMMAND1, COMMAND2 и т.д.

вторник, 2 апреля 2013 г.

Вызов диалоговых окон открытия-сохранения файла и выбора каталога в приложении Visual Studio

Рассмотрим создание стандартных диалогов для выбора файла или каталога в приложении, написанном на Visual C++.
В качестве шаблона будем использовать Dialog based приложение, созданное в предыдущем посте. Это стандартное, "пустое" Dialog based приложение, просто мы там добавили меню и обработчик его команды, которая вызывается когда мы щелкаем по пункту этого меню.
В этот обработчик (функция CCreatingMenuDlg::OnMytopmenuMymenuitem()) вместо старого вставляем новый код:
    CString a(TEXT("All Files (*.*)|*.*||")),
            b(TEXT("My File Dialog")),
            c(TEXT("Your file name is :"));

    CFileDialog fileDlg( TRUE,
                        NULL,
                        NULL,
                        OFN_ALLOWMULTISELECT|OFN_HIDEREADONLY,
                        a,
                        this,
                        sizeof(OPENFILENAME)
                        );
    fileDlg.m_ofn.lpstrTitle = b;

    if ( fileDlg.DoModal() == IDOK)
    {
        CString szlstfile = fileDlg.GetPathName();
        CString res;
        res =  c + szlstfile;
        AfxMessageBox(res );
        ofstream flow1 (szlstfile);
        flow1 << "Ты сделал это!!!";
    }
При выборе этого пункта меню будет вызываться диалоговое окно, которое позволяет выбрать или создать файл, куда будет записана тестовая строка. Будьте осторожны при тестировании, чтобы не затереть какой-нибудь полезный файл.
Для работы с типом  ofstream (для демонстрируемой функциональности вообще-то нам последние две строчки кода что приведен выше не нужны) включим в файл CreatingMenu.h следующее:
#include<fstream>
#include
<iostream>
#include<string>
using namespace std;
Все. Теперь можно скомпилировать и запустить на выполнение.
Теперь напишем код для вызова диалога для выбора директории. Для этого в обработчик CCreatingMenuDlg::OnMytopmenuMymenuitem() вместо предыдущего вставим такой код:
    BROWSEINFO bi = { 0 };
    bi.lpszTitle = _T("Pick a Directory");
    bi.ulFlags |= BIF_NEWDIALOGSTYLE;
    LPITEMIDLIST pidl = SHBrowseForFolder ( &bi );
    if ( pidl != 0 )
    {
        TCHAR path[MAX_PATH];
        if ( SHGetPathFromIDList ( pidl, path ) )
        {
            //_tprintf ( _T("Selected Folder: %s\n"), path );
            CString a(path);
            CString res = TEXT("Your selected path is: ") + a;
            AfxMessageBox(res);
        }

         IMalloc * imalloc = 0;
        if ( SUCCEEDED( SHGetMalloc ( &imalloc )) )
        {
            imalloc->Free ( pidl );
            imalloc->Release ( );
        }
    }
 Компилируем и запускаем на выполнение.

Создание меню в Dialog Based приложении Visual Studio

Итак. Запускаем Visual Studio 2010. Создаем новый проект с именем CreatingMenu:
File->New->Project... (Ctrl+Shift+N)
Выбираем MFC Application и вводим имя проекта: CreatingMenu
Жмем ОК
Жмем Next >
Выбираем Dialog based
Жмем Next >
Убираем About box
Жмем Next >
Убираем ActiveX controls
 Жмем Next >
Жмем Finish
Видим такое:

(К такому "виду" мы можем придти через меню: View->Resource View (Ctrl+Shift+E))
Удаляем статический текст в центре диалога. Далее делаем щелчок правой кнопкой мыши в Resource View на имени нашего проекта CreatingMenu. Add->Resource...
Выбираем в списке Resource type: пункт Menu. Жмем кнопку New и видим такое:
Магической комбинацией щелчков мышью создаем MyTopMenu и MyMenuItem:
Делаем щелчок правой клавишей мыши на MyMenuItem и выбираем пункт
Add Event Handler...
Из списка Class list выбираем CCreatingMenuDlg и жмем Add and edit
Добавляем код в пустое тело обработчика событий:
    CString myMessage(TEXT("Ты сделал это!!!"));
    AfxMessageBox(myMessage);
Добавляем в класс CCreatingMenuDlg новый член:
 private:
    CMenu m_menu;
а в функцию CCreatingMenuDlg::OnInitDialog() код:
 if (!m_menu.LoadMenu(IDR_MENU1))
     AfxThrowResourceException();
  SetMenu(&m_menu);
Далее компилируем и запускаем на выполнение:

 Мы сделали это!!!

понедельник, 25 марта 2013 г.

Основы MPI

Что такое MPI? Смотрим википедию. Здесь же мы придержимся нашего жанра "шпоргалки".
Шесть "главных" MPI команд:
  • MPI_Init(int *argc, char ***argv)
  • MPI_Finalize()
  • MPI_Comm_size(MPI_Comm comm, int *size)
  • MPI_Comm_rank(MPI_Comm comm, int *rank)
  • MPI_Get_processor_name(char *name, int *resultlen)
  • MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
  • MPI Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
Основные понятия MPI:
  • типы MPI:
    MPI_CHAR, MPI_SHORT, MPI_INT, MPI_LONG, MPI_UNSIGNED_CHAR, MPI_UNSIGNED_SHORT, MPI_UNSIGNED, MPI_UNSIGNED_LONG, MPI_FLOAT, MPI_DOUBLE, MPI_LONG_DOUBLE
  • MPI коммуникатор
  • MPI tag
  • MPI Processes
Проиллюстрируем это обилие информации классическим приветствием миру. Создаем файл helloworld.c следующего содержания:
#include
#include
int main(int argc, char ** argv) {
    int size,rank;
    int length;
    char name[80];

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD,&rank);
    MPI_Comm_size(MPI_COMM_WORLD,&size);
    MPI_Get_processor_name(name,&length);
    printf("Hello MPI! Process %d of %d on %s\n",size,rank,name);
    MPI_Finalize();
    return 0;
}
Компилируем и запускаем на выполнение:
#mpicc helloworld.c -o helloworld
#mpirun -np 2 ./helloworld
Запустить на выполнение можно так:
#mpirun -np 2 -machinefile hostfile ./helloworld
где hostfile - файл с таким, например, содержанием:
aa slots=4
bb slots=4
cc slots=4

(взято с мануала к команде mpirun) aa, bb, cc - имена узлов. Поварируйте число после ключа -np, узнаете много интересного.
Рассмотрим передачу сообщений между процессами. Отредактируем файл helloworld.c следующим образом:
#include
#include
int main(int argc, char ** argv) {
    int size,rank;
    int length;
    char name[80];
    int dest = 0;
    int tag = 999;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD,&rank);
    MPI_Comm_size(MPI_COMM_WORLD,&size);
    MPI_Get_processor_name(name,&length);
    printf("Hello MPI! Process %d of %d on %s\n",size,rank,name);
    if (rank != 0 ) { /* I’m a client */
        MPI_Send(name,80,MPI_CHAR,dest,tag,MPI_COMM_WORLD);
    }else{ /* I’m the server (rank == 0) */
        MPI_Status status;
        int source;
        for(source = 1; source < size; source++) {
 
MPI_Recv(name,80,MPI_CHAR,source,tag,MPI_COMM_WORLD,&status);
            printf(" mesg from %d of %d on %s\n",source,size,name);
        }
    }   
    MPI_Finalize();
    return 0;
}
Компилируем и запускаем на выполнение теми же командами, что и первый пример.
Оба примера компилировались и тестировались на компьютере с Debian Wheezy:
#uname -a
Linux myhost 3.2.0-4-686-pae #1 SMP Debian 3.2.39-2 i686 GNU/Linux

среда, 13 марта 2013 г.

Дискретное преобразование Фурье в fftw3 и в CUDA

Если вам вдруг понадобилось вычислить дискретное преобразование Фурье, то для этой цели прекрасно подойдут два инструмента: http://www.fftw.org/ и CUDA. К достоинствам первого можно отнести его открытость, специализация именно на преобразовании Фурье, предельная простота установки. 
Рассмотрим для начала пример с использованием fftw.
Создадим файл fftw3.c следующего содержания:
#include <stdlib.h>
#include
<stdio.h>
#include <fftw3.h>
Компилируем его с помощью следующей команды:
#gcc fftw3.c -o fftw3 -lfftw3 -lm
 Как видите, если убрать код, выводящий данные на стандартный вывод, то остальной код, выполняющий главную функциональность, будет смотреться предельно просто и ясно. Всего несколько строчек. Для завершения картины запускаем программу:
#./fftw3
и изучаем вывод. Должны увидеть, что обратное преобразование восстанавливает входные данные для прямого преобразования.
Далее рассмотрим решение этой же задачи средствами CUDA. К недостаткам этого метода можно отнести не всегда простую установку CUDA  и обязательное наличие видеокарты, поддерживающую эту технологию. К достоинствам отнесем всю мощь данной технологии. Код взят из примеров, поставляемых вместе с тулкитом с сайта NVIDIA. Называется он (код, с помощью которого была написана программа, исходники которой далее приводятся) иронично simpleCUFFT. Хотя простоты там, на мой взгляд нет совсем, потому что пример, показывающий как пользоваться преобразованием Фурье, завален кодом, делающим конволюцию сигнала с фильтром (зачем это людям, изучающим CUDA?), и выковырять пару строчек, которые и делают прямую задачу примера, было для меня не просто.  За деревьями леса не видно. Поэтому, думаю, этот пост будет полезен.
Итак, создаем файл truelySimpleFFT.cu следующего содержания:
#include<stdlib.h>
#include
<stdio.h>
#include<string.h>
#include<math.h>

#include
<cuda_runtime.h>
#include <cufft.h>

typedef float2 Complex;
#define N_ELEM 10
int
main(int argc, char **argv)
{
    Complex *h_signal;
    Complex *d_signal;
    int mem_size;
    cufftHandle plan;
   
    printf("[truely simpleCUFFT] is starting...\n");
    //Выделяем память для входных данных на хосте
    h_signal = (Complex *)malloc(sizeof(Complex) * N_ELEM);

    //Инициализируем входные данные
    for (unsigned int i = 0; i < N_ELEM; ++i){
        h_signal[i].x = (float)i;
        h_signal[i].y = 0;
        printf("%f\t%f\n", h_signal[i].x, h_signal[i].y);
    }  
   
    //Выделяем память для входных данных на видеокарте и копируем их туда
    mem_size = sizeof(Complex) * N_ELEM;     
    cudaMalloc((void **)&d_signal, mem_size);                                  
    cudaMemcpy(d_signal, h_signal, mem_size, cudaMemcpyHostToDevice);                 
     
    //Собственно выполняем преобразование Фурье
    printf("Transforming signal cufftExecC2C\n");
    cufftPlan1d(&plan, N_ELEM, CUFFT_C2C, 1);                        
    cufftExecC2C(plan, (cufftComplex *)d_signal, (cufftComplex *)d_signal, CUFFT_FORWARD);
   
    //Копируем результат вычисления с видеокарты на хост
    cudaMemcpy(h_signal, d_signal, mem_size, cudaMemcpyDeviceToHost);
    for (unsigned int i = 0; i < N_ELEM; ++i)
        printf("%f\t%f\n", h_signal[i].x, h_signal[i].y);
   
    //Прибираем за собой
    cufftDestroy(plan);
    free(h_signal);
    cudaFree(d_signal);
    cudaDeviceReset();
   
    exit(0);
}
 
Компилируем его с помощью следующих двух команд:
#nvcc  -o simpleCUFFT.o -c truelySimpleFFT.cu
#g++ -o simpleCUFFT simpleCUFFT.o -lcudart -lcudart -lcufft
Необходимо отметить следующее. Если вы устанавливали SDK с сайта NVIDIA "руками", то вам надо вставить в выше приведенные команды ключи -L и -I с соответствующими путями к заголовочным файлам и библиотекам, которые вы указывали при установке (а также nvcc с полным путем). Я устанавливал с помощью репозитария (пользуюсь Дебиан) и в моем случае надо вводить команды как написано выше.
Также обратите внимание на подобие кода, выполняющего преобразование Фурье в обоих примерах.
Запускаем на выполнение командой:
 #./simpleCUFFT
Обе программы компиллировались и ваполнялись на Debian GNU/Linux 7.0 (wheezy).

воскресенье, 10 марта 2013 г.

Простейшая генерация случайных чисел в языке Си

Приведем простейшую функцию генерации случайных чисел в языке Си в диапазоне от нуля до единицы:
double
frand ( void ){
  double value;
  value = ( ( double ) rand ( ) / ( RAND_MAX ) );
  return value;
}
У Кернигана и Ричи используется конструкция ( ( double ) rand ( ) / ( RAND_MAX + 1 ) ), но она может привести к отрицательным величинам у случайных чисел.

Простейшая работа со временем в языке Си

Допустим нам необходимо получить строку выводящую время на "человеческом" языке. Как исходный шаблон можем использовать следующую функцию:
void
timestamp ( void )
{
    static char time_buffer[100];
    const struct tm *tm;
    size_t len;
    time_t now;

    now = time ( NULL );
    tm = localtime ( &now );
    len = strftime ( time_buffer, 100, "%d %B %Y %I:%M:%S %p", tm );
    printf ( "%s\n", time_buffer );

    return;
}

Пусть теперь нам надо посмотреть, как много времени забирает выполнение того или иного участка кода. Для решения этой задачи будет удобным следующий пример:
#include<stdio.h>
#include
<stdlib.h>
#include<time.h>

Расширения языка Си в CUDA

Имеется четыре типа расширений языка Си в CUDA. Это:
  1. Квалификаторы типа функции, определяющие, где эта функция будет выполняться - в центральном процессоре или на видеокарте, и возможно ли вызывать эту функцию из центрального процессора или видеокарты.
  2. Квалификаторы типа переменных, определяющие распределение памяти для этих переменных на видеокарте.
  3. Директива, определяющая как ядро выполняется на видеокарте.
  4. Четыре встроенных переменных, определяющих размерность грида, размерность блока, индекс блока и индекс нити.
Рассмотрим эти расширения подробней.
I.Квалификаторы типа функций
  1. __device__ квалификатор говорит, что функция выполняется на видеокарте и может быть вызвана только из видеокарты.
  2. __global__ квалификатор объявляет функцию выполняемой в  центральном процессоре и вызываемой только процессором. Квалифицирование функции только __host__ аналогично объявлению функции без какого-то ни было квалификатора. Однако функцию можно квалифицировать комбинацией __host__ __device__. Тогда функция будет откомпилирована и для центрального процессора и для видеокарты.  
 Ограничения:
  •  функции квалифицированные __device__ и __global__ не поддерживают рекурсии.
  •  функции квалифицированные __device__ и __global__ не могут декларировать внутри себя статические переменные.
  • функции квалифицированные __device__ и __global__ не могут иметь переменное количество аргументов.
  • функции квалифицированные __device__ не могут вызываться по их адресу.
  • функции не могут быть одновременно квалифицированы как __global__ и __host__.
  • функции квалифицированные как __global__ обязаны возвращать пустой тип.
  • функции квалифицированные как __global__ должны специфицировать  свою конфигурацию выполнения.
  • функции квалифицированные как __global__ выполняются синхронно.
  • параметры функций квалифицированных как __global__ передаются через общую память в видеокарту и ограничены 256-ю байтами.
II. Квалификаторы переменных.
  1. __device__ квалификатор говорит, что переменная размещается на видеокарте. Может использоваться совместно с другими квалификаторами типа переменных. Если таковых нет, то это означает, что переменная размещена в глобальной памяти, имеет время жизни равное времени жизни приложения и доступна из нитей в гриде и с центрального процессора через библиотеку времени исполнения.
  2. __constant__ квалификатор опционально используемый предыдущим квалификатором объявляет, что переменная размещается в области постоянной памяти, имеет время жизни равное времени жизни приложения и доступна из нитей в гриде и с центрального процессора через библиотеку времени исполнения.
  3. __shared__ квалификатор опционально используемый с  __device__ квалификатором объявляет, что перменная размещается в общей памяти блока нитей, имеет время жизни равное времени жизни блока и доступна только из нитей данного блока.
Ограничения:
  • Эти квалификаторы не допустимы для членов struct и union, в формальных параметрах функций и локальныхперменных внутри функций, что выполняются на центральном процессоре.
  • __shared__ и __shared__ не могут использоваться вместе.
  • __shared__ и __shared__ переменные подразумеваются статическими.
  • __constant__ переменные не могут использоваться внутри видеокарты, только на центральном процессоре. Поэтому они имеют область видимости файла.
  • __shared__ переменные не могут иметь инициализацию как часть их декларации.
III. Конфигурация выполнения.
Любой вызов функции __global__ должен специфицировать конфигурацию выполнения этой функции. Конфигурация выполнения функции определяет размерность грида и блоков, которые будут использоваться при вызове функции на видеокарте. Эта конфигурация задается аргументами между тройными угловыми скобками. <<>>. Эта конструкция помещается между именем функци и списком передаваемым в функцию списком "обычных" аргументов.
Описание аргументов в угловых скобках следующее:
  1. Dg - имеет тип dim3 и определяет размерность и длину грида так что выражение Dg.x*Dg.y равно числу запущенных блоков.
  2. Db - имеет тип dim3 и определяет размерность и длину каждого блока, так что выражение Db.x*Db.y*Db.z равно числу нитей в каждом блоке.
  3. Ns - имеет тип size_t и определяет количество байтов в общей памяти, которая выделяется динамически при данном вызове функции дополнительно к ее статической памяти. Ns - необязательный аргумент со значением по умолчанию раным нулю.
Аргументы конфигурации выполнения вычисляются раньше  "обычных" аргументов функции.
IV. Встроенные переменные
  1. gridDim - переменная типа dim3. Содержит размерность грида.
  2. blockIdx - переменная типа uint3. Содержит индекс блока в гриде.
  3. blockDim - переменная типа dim3. Содержит размерность блока.
  4. threadIdx - переменная типа uint3. Содержит индекс нити в блоке.
Ограничения:
  • не допустима операция получения адреса от любой встроенной переменной.
  • не допустима операция присваивания для любой встроенной переменной.

суббота, 26 января 2013 г.

Cовместное использование языков Си и Фортран.

В UNIX системах языки Си и Фортран могут использоваться вместе, так как между этими языками возможны прямые вызовы функций. При этом, правда, необходимо тщательно учитывать совместимость типов передаваемых аргументов. Обычно соответствие между типами в Си и Фортране можно представить так:
signed char    INTEGER*1
short               INTEGER*2
int                   INTEGER
long                INTEGER*4
float                REAL
double            DOUBLE PRECISION
void sub_        SUBROUTINE SUB()
float  fun_()     REAL FUNCTION FUN
Также следует помнить о следующем: 
  • в Фортране аргументы в функции всегда передаются ссылками;
  • В Си массивы передаются через адрес;
  • Порядок индексации многомерных массивов различен для Си и Фортрана - в Си элементы массива располагаются построчно, в Фортране - по столбцам;
  • Имена функций в Фортране имеют в своем конце "скрытый" знак подчеркивания '_' (его нет в исходном тексте, но он добавляется компилятором), поэтому, если некая фунция вызывается из Фортрана как fun(), то эта же функция из Си вызывается как fun_() ; 
  • В языке Си длина строки определяется положением нулевого байта, в Фортране же строки имеют фиксированную длину. Поэтому при передаче строки из Си кода в Фортран необходимо передавать также ее длину. В случае же передачи строки из Фортран кода в Си необходимо добавить нуль в конец строки;
Рассмотрим два случая: когда си код вызывается из фортрана и , наоборот, когда из фортран кода вызывается си код.
Файл cCode1.c:
#include<stdio.h>
void
c_function_(){
     printf("Hello from C code!\n");
}
 Файл fortranCode1.f90:
program hello
    implicit none
    call c_function()
    write (*,*)"Hello from FORTRAN code!"
end program hello
 Компилируем и запускаем на выполнение:
#gfortran -O2 -g -o fortranCode1.o -c fortranCode1.f90
#gcc -O2 -g -o cCode1.o -c cCode1.c
#gcc -L /usr/lib/gcc/i486-linux-gnu/4.7/ -lgfortran fortranCode1.o cCode1.o -o hello1
#./hello1
Нужно, чтобы присутствовала библиотека libgfortran.so
 /usr/lib/gcc/i486-linux-gnu/4.7/ - это путь, по которомуона у меня лежит. У вас может быть другой путь.
Обратный случай:
Файл cCode2.c:
#include<stdio.h>
void
main(){
     fortran_function_();
     printf("Hello from C code!\n");
Файл fortranCode2.f90:
subroutine fortran_function
    implicit none
    write (*,*)"Hello from FORTRAN code!"
end subroutine fortran_function
Компилируем и запускаем на выполнение:
#gfortran -O2 -g -o fortranCode2.o -c fortranCode2.f90
#gcc -O2 -g -o cCode2.o -c cCode2.c
#gcc -L /usr/lib/gcc/i486-linux-gnu/4.7/ -lgfortran fortranCode2.o cCode2.o -o hello2
#./hello2
Теперь сделаем вызов функции с передачей параметров разных типов.
Файл cCode3.c:
#include<stdio.h>
void
c_function_(signed char* a1, short* a2, int* a3, long int* a4){
     printf("in called c function: \n%d\t%d\t%d\t%ld\n", *a1, *a2, *a3, *a4);
}
 Файл fortranCode3.f90:
program hello
    implicit none
    integer*1 a1
    integer*2 a2
    integer*4 a3
    integer*8 a4
    a1=1
    a2=22
    a3=333
    a4=4444
    call c_function(a1, a2, a3, a4)
end program hello
 #gfortran -O2 -g -o fortranCode3.o -c fortranCode3.f90
#gcc -O2 -g -o cCode3.o -c cCode3.c
#gcc -L /usr/lib/gcc/i486-linux-gnu/4.7/ -lgfortran fortranCode3.o cCode3.o -o hello3
#./hello3
 Файл cCode4.c:
#include<stdio.h>
void
main(){
     signed char a1 = 1;
     short            a2 = 22;
     int                 a3 = 333;
     long              a4 = 4444;
     fortran_function_(&a1, &a2, &a3, &a4);
Файл fortranCode4.f90:
subroutine fortran_function(f1, f2, f3, f4)
    implicit none
    integer*1 f1
    integer*2 f2
    integer*4 f3
    integer*4 f4
    write (*,100)f1,f2,f3,f4
100 format(4i5)
end subroutine fortran_function
#gfortran -O2 -g -o fortranCode4.o -c fortranCode4.f90
#gcc -O2 -g -o cCode4.o -c cCode4.c
#gcc -L /usr/lib/gcc/i486-linux-gnu/4.7/ -lgfortran fortranCode4.o cCode4.o -o hello4
#./hello4
Теперь продемонстрируем передачу символьных строк из Си в Фортран и обратно.

Файл cCode5.c:
#include<stdio.h>
void
     c_function_(char* string){
     printf("%s\n", string);
}
 Файл fortranCode5.f90:
program hello
    implicit none
    character*37 string
    string="Created in fortran, outputed from C!"
    string(37:37)=char(0)
    call c_function(string)
end program hello
#gfortran -O2 -g -o fortranCode5.o -c fortranCode5.f90
#gcc -O2 -g -o cCode5.o -c cCode5.c
#gcc -L /usr/lib/gcc/i486-linux-gnu/4.7/ -lgfortran fortranCode5.o cCode5.o -o hello5
#./hello5 
Теперь наоборот. Сформируем строку в Си, а напечатаем в Фортране.
Файл cCode6.c:
#include<string.h>
void
main(){
     char string[37];
     int length = sizeof(string);
     strncpy(string, "Created in C, outputed from Fortran!", length);
     fortran_function_(string, &length);
}
Файл fortranCode6.f90:
subroutine fortran_function(string, length)
    implicit none
    character*(*) string
    integer length
    write(*,100)string(1:length)
100 format(a)
    return
end subroutine fortran_function
#gfortran -O2 -g -o fortranCode6.o -c fortranCode6.f90
#gcc -O2 -g -o cCode6.o -c cCode6.c
#gcc -L /usr/lib/gcc/i486-linux-gnu/4.7/ -lgfortran fortranCode6.o cCode6.o -o hello6
#./hello6
Все примеры протестированы в Debian Wheezy.

среда, 16 января 2013 г.

sed: убираем лишнее

При работе с большими текстовыми файлами, хранящими данные для построения графиков, очень часто не нужен весь объем хранимой  информации. Тут на помощь может прийти утилита sed.
Чтобы удалить каждую 5-ю, например, строку, можно сделать так: 
sed -n '0~5d' bigdata.txt > lessdata.txt
А что если мы хотим не удалить каждую 5-ю, а сохранить, удалив остальные строки? Тогда так:
sed -n '0~5p' bigdata.txt > lessdata.txt
Следующая команда делает то же плюс сохраняет первую и последнюю строки:
sed -n '1p;0~5p;$p' bigdata.txt > lessdata.txt 

среда, 9 января 2013 г.

Gnuplot: линейная аппроксимация

Пусть у нас имеется текстовый файл myData.dat состоящий из двух колонок цифр и мы хотим нарисовать два графика: первый, изображенный символами (точками), собственно данные; второй, представленный линией, которая и является линейной аппроксимацией этих данных. Создадим файл myApproximation.plt следующего содержания:
my_linear_fit(x)=a*x+b
fit my_linear_fit(x) 'myData.dat' via a,b
plot 'myData.dat'     title 'my data', my_linear_fit(x) title 'approximation of my data' 
И запустим gnuplot из директории, в которой создали этот файл и введем команду:
>load 'myApproximation.plt'
Для задания диапазонов абсциссы и ординаты пользуем такие команды:
>set xrange [0:100]
>set yrange[0:]
Цифры даны для примера.

Установка сетевого принтера в Debian GNU/Linux 7.0 (wheezy)

После установки (в моем случае) Debian wheezy штатные утилиты гнома почему-то не устанавливали (а, возможно, и не должны) сетевого принтера. Но это не есть большой трудностью. Достаточно доустановить пакет system-config-printer. И после запуска кочанды system-config-printer из-под администратора легко найти и подсоединить принтер

Gnuplot: вывод в несколько окон

Если мы делаем графики в Gnuplot, то по умолчанию вывод этих графиков делается в окно с заголовком "Gnuplot(window id:0)". В других операционных системах заголовок, возможно, другой. Я использую Debian GNU/Linux 7.0 (wheezy). Но суть не в этом. Итак. Если мы дадим команду replot или plot, но с другими чем в предыдущем plot опциями, то наш предыдущий график будет затираться новым. Но что, если я хочу нарисовать еще один график не уничтожая предыдущего? Можно рисовать следующие графики в новом окне. Делается это командой:
>set terminal wxt n
где n - это некое число: 1,2,3 и т.д. Соответственно новое окно будет выводиться с заголовком "Gnuplot(window id:n)".

пятница, 4 января 2013 г.

Два монитора на компьютере с Debian

Хорошо, если у нас есть ноутбук с Debian wheezy. А что если у нас есть второй монитор и мы хотим его подсоединить к нашему ноутбуку? В моем случае второй монитор подсоединился автоматически, но при этом не только на нем вывод был с плохой разрешающей способностью, но и на экране самого ноутбука качество изображения изменилось не вbлучшую сторону. Вобщем встал вопрос: как "правильно" подсоединить второй монитор к нашему ноутбуку? Выполним программу xandr:
#xrandr
В моем случае вывод был такой:
Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 8192 x 8192
LVDS1 connected (normal left inverted right x axis y axis)
   1366x768       60.0 +
   1360x768       59.8     60.0  
   1024x768       60.0  
   800x600        60.3     56.2  
   640x480        59.9  
VGA1 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 509mm x 286mm
   1920x1080      60.0*+
   1280x1024      75.0     60.0  
   1152x864       75.0  
   1024x768       75.1     60.0  
   800x600        75.0     60.3  
   640x480        75.0     60.0  
   720x400        70.1  
HDMI1 disconnected (normal left inverted right x axis y axis)
DP1 disconnected (normal left inverted right x axis y axis)
Достаточно прозрачный вывод. Если выполнить программу xrandr со следующими параметрами (их значения выбираем из вывода предыдущей команды):
#xrandr --output VGA1 --mode 1920x1080 --output LVDS1 --off
то графический вывод будет осуществляться только на присоединенный монитор. Если выполнить команду:
#xrandr --output VGA1 --off --output LVDS1 --mode 1366x768 --pos 0x0
то вывод будет выполняться только на экран ноутбука. Общая идея понятна. Теперь мы хотим следующее. При включении компьютера, если подсоединен второй монитор, нам надо, чтобы включался только этот второй моитор. Если второго монитора нет, то пусть включается только (а что ж еще?) монито ноутбука. Дя этого создаем файл /etc/X11/Xsession.d/45custom_xrandr со следующим содержанием (он запускается при запуске иксов):
xrandr | grep VGA1 | grep " connected "
if [$? -eq 0]; then
      # Внешний монитор отключен
      # Если мы хотим два показывающих монитора
      # xrandr --output VGA1 --mode 1920x1080 --pos 0x0 --output LVDS1 --mode 1366x768 --pos 0x0
      # Если мы хотим включенный только внешний монитор
      xrandr --output VGA1 --mode 1920x1080 --output LVDS1 --off
      if [$? -ne 0]; then
           # Если что-то не так
           xrandr --output LVDS1 --mode auto --output VGA1 --off
      fi
else
      # Внешний монитор не подключен
      xrandr --output LVDS1 --mode 1366x768 --output VGA --off
fi
Вот и все. Написано по материалам, взятым отсюда.