piątek, 27 listopada 2009

Tysiąc insertów na sekundę

Dokładniej 50,000 na typowym komputerze - tak przynajmniej twierdzą twórcy sqlite3 w FAQ. Miałem okazję przekonać się o tym na własnej skórze. W programie o którym wcześniej pisałem chciałbym zapisywać dane o cząsteczkach w danej iteracji algorytmu. Oto kod przed optymalizacją:
void SaveResultsToDatabase(sqlite3* db, Particle* host_agents, const int N, const int generation) {
char stmt[90];
char* errorMsg = NULL;
int returnCode;

int i = 0;
for (i = 0; i < N; ++i) {
sprintf(stmt, "insert into pso values (%d, %d, %f, %f); ",
generation,
i,
host_agents[i].current.x,
host_agents[i].current.y);
returnCode = sqlite3_exec(db, stmt, NULL, NULL, &errorMsg);
if (returnCode != SQLITE_OK) {
if (errorMsg != NULL) {
printf("%s", errorMsg);
sqlite3_free(errorMsg);
} else {
printf("Return code: %d\n", returnCode);
}
}
}
}
W tym przypadku czas wykonania to około minuty dla 1000 wpisów. Każdy insert jest wykonywany, baza czeka aż dane będą bezpieczne i dopiero wykonuje następny wpis. Jednak tak jak to jest opisane w FAQ można tego uniknąć tworząc jedną wielką transakcję zamkniętą w klamry BEGIN TRANSACTION; i COMMIT:
 void SaveResultsToDatabase(sqlite3* db, Particle* host_agents, const int N, const int generation) {
char buffer[90];
char stmt [N * 90];
strcpy(stmt, "BEGIN TRANSACTION; ");
char* errorMsg = NULL;
int returnCode;

int i = 0;
for (i = 0; i < N; ++i) {
sprintf(buffer, "insert into pso values (%d, %d, %f, %f); ",
generation,
i,
host_agents[i].current.x,
host_agents[i].current.y);
strcat(stmt, buffer);
}
strcat(stmt, "COMMIT");
returnCode = sqlite3_exec(db, stmt, NULL, NULL, &errorMsg);
if (returnCode != SQLITE_OK) {
if (errorMsg != NULL) {
printf("%s", errorMsg);
sqlite3_free(errorMsg);
} else {
printf("Return code: %d\n", returnCode);
}
}
}
W tym przypadku czas wykonania jest poniżej sekundy. Niezła optymalizacja.
BREAKING NEWS. Właśnie spróbowałem zapisać 100000 wpisów i okazało się, że trwa to około 1,5 minuty. Nie wiedziałem dlaczego aż tyle. Z początku myślałem, że chodzi o manipulowanie char*, ciągłe przydzielanie pamięci itp, ale potem przypomniało mi się, że ktoś pisał o tym, żeby znaleźć "optimal insert size for your database". Czas zatem na kolejną zmianę kodu:
void SaveResultsToDatabase(sqlite3* db, Particle* host_agents, const int N, const int generation) {
printf("Saving to database\n");
const int partSize = 5000;
char buffer[90];
char stmt [partSize * 90];
int i = 0;
while (i < N) {
strcpy(stmt, "BEGIN TRANSACTION; ");
char* errorMsg = NULL;
int returnCode;

do {
sprintf(buffer, "insert into pso values (%d, %d, %f, %f); ",
generation,
i,
host_agents[i].current.x,
host_agents[i].current.y);
strcat(stmt, buffer);
++i;
} while ((i % partSize != 0) && i < N);
strcat(stmt, "COMMIT");
returnCode = sqlite3_exec(db, stmt, NULL, NULL, &errorMsg);
if (returnCode != SQLITE_OK) {
if (errorMsg != NULL) {
fprintf(stderr, "%s\n", errorMsg);
sqlite3_free(errorMsg);
} else {
fprintf(stderr, "sqlite3 return code: %d\n", returnCode);
}
}
}
}
W obecnej postaci wykonanie kodu trwa 23 sekundy dla 3 iteracji po 100000 cząsteczek, włączając w to przesyłanie danych, obliczenia na karcie graficznej i wyświetlanie komunikatów w terminalu (to chyba najbardziej spowalnia program). Przypuszczam, że jeszcze będę eksperymentować z wielkością wrzutów do bazy danych.

Przeszukiwanie listy argumentów linii poleceń w C

Po długich i wyczerpujących walkach z nvcc (kod skompilowany nvcc nie bardzo się komponuje z g++, nie wiem czemu, innym DZIAŁA - SOA#1) postanowiłem, że algorytm optymalizacji rojem będzie miał postać programu w C, który zapisze wynik działania algorytmu w bazie sqlite3, a ja sobie w innej aplikacji skompilowanej już normalnym kompilatorem będę te dane obrabiał. W międzyczasie okazało się, że fajnie byłoby ustalać liczbę cząsteczek roju i postanowiłem do tego wykorzystać argumenty linii poleceń. Wtedy pojawił się problem jak z argc i argv wyciągnąć to o co mi chodzi. Czynność ta jest wykorzystywana na porządku dziennym w programach, więc istnieje w tym celu odpowiednia procedura, a nazywa się ona getopt() i znajduje się w pliku nagłówkowym unistd.h. W obecnej chwili używam dwóch opcji: -n oraz -o. Obie wymagają argumentu. Oto kod:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char** argv) {
//initialize variables
int N = 10; // Ilosc agentow
int c = 0;
char* dbfilename = NULL;

while((c = getopt(argc, argv, "n:o:")) != -1)
{
switch(c)
{
case 'n':
N = atoi(optarg);
printf("Number of particles: %d\n", N);
if (N <= 0)
{
fprintf(stderr, "Invalid number of particles, setting to default = 10\n");
N = 10;
}
break;
case 'o':
dbfilename = optarg;
printf("Output to file: %s\n", dbfilename);
break;
case '?':
switch(optopt)
{
case 'n':
fprintf(stderr, "Option -n sets number of particles and requires integer argument.\n");
break;
case 'o':
fprintf(stderr, "Option -o sets name of output file and requires a string argument.\n");
break;
default:
fprintf(stderr, "Unknown option: %c\n", optopt);
};
break;
default:
printf("Abort: %d = %c\n", c, c);
abort();
}
}
Przede wszystkim po "przegryzieniu się" przez wszystkie argumenty getopt() zwraca -1, dlatego taki warunek jest w while. W przeciwnym przypadku dostaniemy wartość znaku, który reprezentuje opcję. Opcje jakie przewidujemy w programie podajemy jako trzeci argument. Aby zaznaczyć, że opcja ma wymagany argument należy dodać po niej ":". Aby zaznaczyć, że opcja ma opcjonalny argument dodajemy "::". W switch podejmujemy odpowiednią akcję w zależności od opcji.
Dla -n będzie to zamiana alphanumeric to integer, czyli zamiana łańcucha znaków na liczbę całkowitą, a następnie sprawdzenie czy wartość ta jest większa od 0. Argument dla opcji jest przechowywany w optarg jako łańcuch znaków.
Opcja -o odpowiada za nazwę pliku wynikowego. Jak widać można przypisać wskaźnik optarg do wskaźnika typu char*, czyli nie jest on typu const char* (tak jakby to było w przypadku np printf("Tekst typu const char*"). Dokładniej optarg wskazuje na jeden z elementów argv, więc można go modyfikować. W przypadku błędów podawana jest wartość '?' co może oznaczać brak argumentu dla opcji lub nieznaną opcję.
Opcja default z tego co zrozumiałem nie ma prawa się wydarzyć, ale dodałem tam trochę kodu, żeby dowiedzieć się co się dzieje w razie czego. W końcu dobry programista patrzy w obie strony przed przejściem przez jednokierunkową ulicę.

piątek, 6 listopada 2009

CUDA 2.3 na Ubuntu 9.10 cz. 2

Teraz należałoby sprawdzić czy wszystko jest poprawnie zainstalowane kompilując przykładowe projekty.
Skracając cały proces pokonywania błędów jakie wystąpiły w czasie tego polecenia:
1) CUDA nie działa z kompilatorami w wersji 4.4. Niestety odinstalowanie gcc-4.4 nie wchodziło w grę, gdyż musiałbym również odinstalować świeżo zainstalowane sterowniki. Zainstalowałem więc gcc i g++ w wersji 4.3.
sudo apt-get install gcc-4.3 g++-4.3
Potem próbowałem zmienić makefile, aby zamiast
CXX := g++
CC := gcc
LINK := g++ -fPIC
mieć
CXX := g++-4.3
CC := gcc-4.3
LINK := g++-4.3 -fPIC
Niestety to nic nie dało. Jeden z użytkowników z forum NVIDIA doszedł do wniosku, że zmienne te nadpisywane są w którymś z innych makefile'ów. Cofnąłem zmiany i zamiast tego zmieniłem dowiązanie /usr/bin/gcc z gcc-4.4 na gcc-4.3.
sudo rm /usr/bin/gcc
sudo ln -s -v gcc-4.3 /usr/bin/gcc
Analogiczną operację przeprowadziłem też dla g++. Sprawdziłem czy pod linkiem gcc jest kompilator o właściwej wersji:
gcc --version
gcc (Ubuntu 4.3.4-5ubuntu1) 4.3.4
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2) Potrzebne też będą paczki libxmu-dev libxi-dev freeglut3 freeglut3-dev.

3) Teraz można już budować projekty.
cd ~/Projects/NVIDIA_GPU_Computing_SDK/C
make

Jeśli wszystko dobrze zapamiętałem to nie powinno być żadnych błędów kompilacji i będzie można uruchomić podstawowe programy testujące na obecność urządzenia cuda capable.
daniel@daniel-laptop:~/Projects/NVIDIA_GPU_Computing_SDK/C/bin/linux/release$ ./deviceQuery
CUDA Device Query (Runtime API) version (CUDART static linking)
There is 1 device supporting CUDA

Device 0: "GeForce 8400M GS"
CUDA Driver Version: 2.30
CUDA Runtime Version: 2.30
CUDA Capability Major revision number: 1
CUDA Capability Minor revision number: 1
Total amount of global memory: 267714560 bytes
Number of multiprocessors: 2
Number of cores: 16
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 16384 bytes
Total number of registers available per block: 8192
Warp size: 32
Maximum number of threads per block: 512
Maximum sizes of each dimension of a block: 512 x 512 x 64
Maximum sizes of each dimension of a grid: 65535 x 65535 x 1
Maximum memory pitch: 262144 bytes
Texture alignment: 256 bytes
Clock rate: 0.80 GHz
Concurrent copy and execution: Yes
Run time limit on kernels: Yes
Integrated: No
Support host page-locked memory mapping: No
Compute mode: Default (multiple host threads can use this device simultaneously)

Test PASSED
oraz
Running on......
device 0:GeForce 8400M GS
Quick Mode
Host to Device Bandwidth for Pageable memory
.
Transfer Size (Bytes) Bandwidth(MB/s)
33554432 1473.1

Quick Mode
Device to Host Bandwidth for Pageable memory
.
Transfer Size (Bytes) Bandwidth(MB/s)
33554432 898.1

Quick Mode
Device to Device Bandwidth
.
Transfer Size (Bytes) Bandwidth(MB/s)
33554432 4197.4

&&&& Test PASSED

CUDA 2.3 na Ubuntu 9.10 cz. 1

Kolejne podejście do używania CUDA na Ubuntu. Po kłopotach jakie miałem ze sterownikami NVIDIA na Ubuntu 8.04 postanowiłem przenieść się na najnowsze wydanie 9.10 (flash nie reaguje na kliknięcia, komputer nie wykrywa, że podłączyłem słuchawki - nie ukrywam, że to niemiłe rozczarowanie). Przy okazji zrobiłem upgrade z XP na Windows7.
Najnowsze sterowniki (w wersji 190) zainstalowałem z pomocą tego poradnika z Ubuntu Geek. W skrócie:
sudo add-apt-repository ppa:nvidia-vdpau/ppa
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CEC06767
sudo apt-get update
sudo apt-get install nvidia-190-modaliases nvidia-glx-190 nvidia-settings-190

Należy je jeszcze aktywować w System -> Administracja -> Sterowniki.

Teraz potrzebne będą Cuda Toolkit 2.3 - nie ma wersji dla Ubuntu 9.10, więc wybrałem wersję dla 9.04. Instalacja według instrukcji przebiega bezproblemowo. Potem zainstalowałem CUDA SDK również w wersji 2.3 dla Ubuntu 9.10.

Kolejnym krokiem było dodanie ścieżek cuda toolkit do zmiennych środowiskowych. W pliku ~/.bashrc dodałem:
if [ -d "/usr/local/cuda" ] ; then
PATH="/usr/local/cuda:$PATH"
fi

if [ -d "/usr/local/cuda/lib64" ] ; then
LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH"
fi

if [ -d "/usr/local/cuda/bin" ] ; then
PATH="/usr/local/cuda/bin:$PATH"
fi
Pierwszy if sprawdza czy istnieje /usr/local/cuda, a jeśli tak to dodaje tę ścieżkę do zmiennej systemowej PATH. Kolejny if dodaje ścieżkę do bibliotek odpowiednich dla 64-bitowego systemu. Następnie dodawany jest katalog z plikami wykonawczymi aby móc korzystać z polecenia nvcc bez wpisywania całej ścieżki.
Nie jestem pewien czy w umieściłem ten skrypt w dobrym pliku - jeśli dobrze pamiętam to jest on wywoływany przy każdym włączeniu konsoli. Dobrze by było by dodawało się ono tylko raz przy starcie systemu i tak już zostawało. Po kilku uruchomieniach PATH wygląda tak:
daniel@daniel-laptop:~$ printenv PATH
/usr/local/cuda/bin:/usr/local/cuda:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
Do tej pory wszystko przebiegało gładko i prawdziwe problemy dopiero się zaczną.

wtorek, 27 października 2009

CUDA ruszyła

Pierwszy projekt z wykorzystaniem CUDA mam już za sobą. Sam kod nie był trudny do napisania. Najgorsze było skłonienie Visual Studio do podpowiadania kodu (co do tej pory mi się nie udało - podpowiada kod tylko dla zmiennych i funkcji nieCUDA). Kolejna sprawa, która mnie irytowała to ścieżki dla plików nagłówkowych i bibliotek. Na początku zakładałem projekt w Moje Dokumenty\Visual Studio 2008\Projects i usiłowałem w ustawieniach projektu dodać ścieżki do CUDA SDK. Kompilator wywalał błędy, że nie może znaleźć pliku cuda.h podczas gdy wydawało mi się, że wszystko jest w porządku. Ostatecznie wyszło na to, że następujące ustawienia są prawidłowe (dla konfiguracji Debug):
- Linker -> General -> Additional Library Directories: "C:\CUDA\lib";"C:\Documents and Settings\Yelonek\Ustawienia lokalne\dane aplikacji\NVIDIA Corporation\NVIDIA CUDA SDK\common\lib" (tutaj taka dziwna ścieżka, bo wybrałem "instaluj tylko dla mnie" podczas instalacji pakietu i nie chciało mi się tego odkręcać)
- Linker -> Input -> Additional Dependencies: cudart.lib cutil32d.lib
Następny problem jaki się pojawił to to, że VS nie koloruje składni w plikach cu. Można to obejść w następujący sposób:
W katalogu NVIDIA CUDA SDK\doc\syntax_highlighting znajduje się plik usertype.dat, który należy umieścić w katalogu Microsoft Visual Studio 8\Common7\IDE. Następnie w samym VS należy w Tools... -> Options otworzyć Text Editor w liście po lewej i wybrać File Extension. Wpisujemy cu w polu Extension, ustawiamy edytor na MS Visual C++ i klikamy Add. Po zatwierdzeniu zmian i restarcie programu można się już cieszyć pokolorowaną składnią przy przeglądaniu plików cu.
Istnieje jeszcze jeden sposób na trochę wygodniejsze pisanie programów. Patent ten można znaleźć na innych stronach i blogach, ale też go tu opiszę. Polega on na tym, że program piszemy w pliku C, a podczas budowania projektu plik c kopiuje się do pliku cu, a dopiero plik cu jest budowany za pomocą nvcc. Dokonać tego można definiując własne zasady budowania plików. Po kliknięciu ppm na projekt w Solution Explorerze wybieramy Custom Build Steps... Tworzymy nowy plik zasad (New Rule File), podajemy nazwę jaka będzie wyświetlana w VS, nazwę pliku oraz ściężkę w jakiej chcemy go zapisać. Dodajemy nową zasadę (Add Build Rule). Pierwsza zasada to kopiowanie pliku c do cu.
Command line:copy $(InputFileName) $(InputName).cu
Display Name: Zamiana pliku c na cu
Execution Description: $(InputFileName) ------> $(InputName).cu
File Extensions: *.c
Name: Zamiana pliku c na cu
Outputs: $(InputName).cu
Show Only Rule Properties: True
Supports File Batching: False
Druga zasada to kompilacja plików cu za pomocą nvcc:
Additional dependencies: $(CUDA_INC_PATH)";"../../common/inc"
Command Line: "$(CUDA_BIN_PATH)\nvcc.exe" -ccbin "$(VCInstallDir)bin" -c -D_DEBUG -DWIN32 -D_CONSOLE -D_MBCS -Xcompiler /EHsc,/W3,/nologo,/Wp64,/Od,/Zi,/RTC1,/MTd -I"$(CUDA_INC_PATH)" -I../../common/inc -o $(ConfigurationName)\$(InputName).obj $(InputFileName)
Display Name: Kompilacja NVIDIA C for CUDA
Execution Description: Wywoływanie NVCC
File Extension: *.cu
Name: Kompilacja plików CUDA
Outputs: $(ConfigurationName)\$(InputName).obj
Show Only Rule Properties: True
Supports File Batching: False
Teraz tworzymy plik main.c w którym piszemy kod aplikacji. Kopiujemy go (tylko za pierwszym razem) i nazywamy main.cu, a następnie dodajemy do projektu. We właściwościach pliku main.c zamieniamy Tool z C/C++ Compiler na "Zamiana c na cu", a we właściwościach plik main.cu wybieramy "Kompilacja NVIDIA C for CUDA". Od tej pory można już budować projekt za pomocą F6 bez żadnych problemów.
Należy przy tym pamiętać, aby zmian dokonywać w pliku C, a nie CU!!! Gdyby kompilacja projektu się nie udała, to dostaniemy błędy tak jak zawsze, ale dwukrotne kliknięcie przeniesie nas do pliku CU, a nie C!!! Jeśli o tym zapomnimy i zmienimy plik cu, a następnie ponownie zbudujemy projekt wszystko będzie działać: plik c jest niezmieniony, więc VS go ponownie nie buduje (więc plik c nie zostanie skopiowany na plik cu). Załóżmy teraz, że zamknęliśmy projekt, otwieramy go ponownie i edytujemy plik c. Co się dzieje? Plik cu został nadpisany, znika kod pracowicie przez nas napisany i ponownie pojawiają się błędy kompilatora z którymi walczyliśmy przed zamknięciem programu. Można tak stracić parę godzin pracy, więc radzę uważać.

Na koniec przykładowy kod:
#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>

// Struktura danych pojedynczego agenta
struct Agent
{
float x;
float y;
};

// Kernel który będzie wykonywał się na karcie
__global__ void Fitness(Agent* agent, int N, float* results)
{
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < N)
{
results[idx] = 2 * agent[idx].x * agent[idx].x - agent[idx].x + 3;
}
}

// Funkcja ktora bedzie wykonywac sie na hoscie
int main(void)
{
// Deklaracja zmiennych
const int N = 10000; // Ilosc agentow
Agent *host_agents = NULL, *device_agents = NULL; // Wskaznik do tablic u hosta (_h) i na karcie (_d)
size_t size_agents = N * sizeof(Agent); // Obliczenie rozmiaru tablicy agentow w bajtach

float *host_results = NULL, *device_results = NULL; // Wskaznik do tablic z wynikami
size_t size_results = N * sizeof(float); // Obliczenie rozmiarow tablicy wynikow w bajtach

// Przydzial pamieci
host_agents = (Agent *)malloc(size_agents); // Przydzial pamieci dla agentow na hoscie
cudaMalloc((void **) &device_agents, size_agents); // Przydzial pamieci dla agentow na karcie
host_results = (float *)malloc(size_results); //Przydzial pamieci na wyniki na hoscie
cudaMalloc((void **) &device_results, size_results); //Przydzial pamieci na wyniki na karcie

// Wpisanie wartosci poczatkowych do tablicy
for (int i=0; i < N; i++)
{
host_agents[i].x = (float)(rand()%100);
host_agents[i].y = (float)(rand()%100);
}

//Skopiowanie tablicy z hosta do karty
cudaMemcpy(device_agents, host_agents, size_agents, cudaMemcpyHostToDevice);
// Wywolanie kernela
int block_size = 4;
int n_blocks = N/block_size + (N%block_size == 0 ? 0:1);

Fitness <<< n_blocks, block_size >>> (device_agents, N, device_results);

// Odebranie wyników z karty i zapisanie ich w tablicy na hoscie
cudaMemcpy(host_results, device_results, size_results, cudaMemcpyDeviceToHost);

// Wypisz wyniki
for (int i = 0; i < N; i++)
printf("%05d: f(%04.4f, %04.4f) = %04.4f;\n", i, host_agents[i].x, host_agents[i].y, host_results[i]);
// Cleanup
free(host_agents);
free(host_results);
cudaFree(device_agents);
cudaFree(device_results);
system("pause");
}

wtorek, 20 października 2009

CUDA NVidia

Niedawno otrzymałem zadanie wykorzystania CUDA w algorytmie optymalizacji polegającym na przeszukiwaniu przestrzeni rozwiązań za pomocą "roju". Zaletą CUDA jest wykorzystanie procesora graficznego do przeprowadzania równoległych obliczeń na wielu zmiennych (przynajmniej w tej chwili tak to rozumiem).
Pomyślałem, że mogę wykonać ten projekt na Ubuntu 8.04.
Udałem się na stronę NVIDIA, gdzie w tej chwili znajdują się sterowniki w wersji 185 oraz CUDA Toolkit 2.2.
Niestety poniosłem porażkę - nie umiałem zainstalować sterowników. Po każdej instalacji Xy wstawały w trybie niskiej jakości grafiki. Doczytałem w internecie, że envyNG instaluje sterowniki NVIDIA wraz z biblioteką libcuda. Za pomocą tego programu udało mi się zainstalować sterowniki w wersji 173. Następnie zainstalowałem CUDA Toolkit oraz CUDA SDK. Zacząłem budować przykładowe projekty. Po wykonaniu make dostałem informację, że nie można znaleźć bibliotek libglut, więc zainstalowałem ją z repozytorium - `sudo apt-get install libglut3 libglut3-dev`. Następnym problemem okazał się projekt threadMigration - tutaj po prostu zmieniłem nazwę Makefile na Makefile-renamed i tego projektu nie uda mi się odpalić. W końcu wszystkie projekty zbudowane i wchodzę do katalogu /home/daniel/NVIDIA_CUDA_SDK/bin/linux/release/. Tutaj najpierw uruchamiam ./deviceQuery z następującym rezultatem:
CUDA Device Query (Runtime API) version (CUDART static linking)
There is 1 device supporting CUDA

Device 0: "GeForce 8400M GS"
CUDA Capability Major revision number: 1
CUDA Capability Minor revision number: 1
Total amount of global memory: 267714560 bytes
Number of multiprocessors: 16
Number of cores: 128
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 16384 bytes
Total number of registers available per block: 8192
Warp size: 32
Maximum number of threads per block: 512
Maximum sizes of each dimension of a block: 512 x 512 x 64
Maximum sizes of each dimension of a grid: 65535 x 65535 x 1
Maximum memory pitch: 262144 bytes
Texture alignment: 256 bytes
Clock rate: 0.80 GHz
Concurrent copy and execution: Yes
Run time limit on kernels: No
Integrated: Yes
Support host page-locked memory mapping: Yes
Compute mode: Default (multiple host threads can use this device simultaneously)

Test PASSED

Press ENTER to exit...

Co oznacza, że mam urządzenie "CUDA Capable". Następny test na porozumiewanie się urządzenia z systemem: ./bandwidthTest
Running on......
device 0:GeForce 8400M GS
Quick Mode
Host to Device Bandwidth for Pageable memory
.
Transfer Size (Bytes) Bandwidth(MB/s)
33554432 856.9

Quick Mode
Device to Host Bandwidth for Pageable memory
.
Transfer Size (Bytes) Bandwidth(MB/s)
33554432 895.4

Quick Mode
Device to Device Bandwidth
.
Transfer Size (Bytes) Bandwidth(MB/s)
33554432 4214.5

&&&& Test PASSED

Press ENTER to exit...


Zatem teoretycznie wszystko dobrze, ale nie mogę uruchomić żadnego przykładu. Najczęstszym błędem jest "cudaSafeCall() Runtime API error in file , line 51 : feature is not yet implemented." Wynika z tego, że CUDA 2.2 nie da rady działać na obecnych sterownikach i będę musiał walczyć z tymi ściągniętymi ze strony NVIDIA.

EDIT:
Z ostatniej chwili: udało mi się zainstalować sterowniki na XP. Pomogło wybranie sterowników dla notebooków. Pozwala to wykorzystywać CUDA w wersji 2.2.

piątek, 18 września 2009

Fstab i montowanie podkatalogów

Postanowiłem dzisiaj, że dysk z XPkiem będzie się montował przy każdym starcie systemu. Docelowo chcę ujednolicić katalogi ściągania, obrazów, kodu itd., tak abym nie musiał przeszukiwać raz dysku z XP, raz dysku z Ubuntu w poszukiwaniu tego czym się akurat zajmuję. Na przykład mogę pracować nad sprawozdaniem w OO.org w Windowsie i w Ubuntu, ale nie mam dostępu do partycji ext3 z Windowsa. Dlatego podlinkuję sobie Moje Dokumenty do /home/daniel, a w tym celu potrzebuję odpowiednich wpisów w fstab.

Było z tym trochę problemu, ale nic czego nie załatwiłoby użycie wyszukiwarki.
Po kolei:
Można wpisać "sudo fdisk -l" aby dostać listę wszystkich dysków. Jako że mój dysk jest wewnętrzny i nie ma szansy, aby zmieniła się jego nazwa, to mógłbym odwołać się właśnie po niej: /dev/sda1/. Jednak inne dyski w fstab odwołują się do UUID dysku, więc ja też tak zrobię. Taka mała dygresja: mam dwa dyski zewnętrzne i pendrive'a. Mogę kazać im montować się nie w katalogu /media/disk i /media/disk1, ale na przykład w katalogu /media/czarny i /media/srebrny w zależności od tego który z nich jest podłączony.
Aby uzyskać UUID dysku należy wpisać:
sudo vol_id /dev/sda1
W moim przypadku dostałem:
ID_FS_UUID=5E543A8F543A69C3
Teraz trzeba otworzyć fstab:
gksu gedit /etc/fstab
i na końcu dodać:
#dysk z XP
UUID=5E543A8F543A69C3 /media/XP ntfs-3g defaults 0 0
Ja oddzielam kolumny tabem, ale można też spacją. Po kolei są to: identyfikator dysku, punkt montowania, system plików, opcje i kolejne jakieś 2 parametry, które przeważnie są zerami. Coś związanego z częstotliwością jakiejś czynności, ale nie wiem jakiej bo nie zagłębiałem się w temat. ;) Alternatywnym wpisem może być:
/dev/sda1 /media/XP ntfs-3g defaults 0 0
Tutaj odwołuję się do dysku po jego nazwie. Więcej informacji po czym jeszcze można się odwoływać znajduje się w internecie.
Na koniec zapisujemy zmiany i wracamy do konsoli. Teraz trzeba dać znać systemowi, żeby przemontował wszystkie dyski zgodnie z nowym plikiem fstab:
sudo mount -a
No i błąd:
mount: mount point /media/XP does not exist
Zatem trzeba naprawić to niedopatrzenie:
sudo mkdir /media/XP
Tym razem sudo mount -a nie wyrzuca żadnych komunikatów i można cieszyć się zamontowanym dyskiem.

Teraz przykładowe dowiązanie katalogu z obrazami:
/media/XP/Documents\040and\040Settings/Yelonek/Moje\040dokumenty/Moje\040obrazy /home/daniel/Obrazy none bind 0 0
Co to za \040? Otóż jeśli w nazwie występuje spacja, to zamiast niej dajemy odpowiadający jej "escape sequence". Jest to wartość 32(kod ANSI dla spacji) w systemie ósemkowym. Gdyby wstawić tam zwykłą spację, to po wywołaniu sudo mount -a dostaniemy komunikat, że "fstab is bad", bo program uzna spację za przejście do następnej kolumny.

Ponowne wywołanie sudo mount -a spowoduje, że w katalogu ~/Obrazy zobaczymy plik z Moje obrazy z dysku XP. Ale moment! Przecież w Obrazy miałem kilka innych plików na dysku Ubuntu, a teraz ich nie widać. No tak, nie widać ich, bo teraz Obrazy są dowiązane do Moje obrazy. Nie wszystko jednak stracone. Co zatem zrobić? Można skasować linijkę odpowiadającą za to dowiązanie i zresetować komputer... Ale to bardzo niewygodne. :] Dlatego należy wpisać:
sudo umount ~/Obrazy
i znowu widzimy stare pliki. Można je przenieść do katalogu Moje obrazy i ponownie dowiązać katalogi. Teraz już wszystko jest tak jak być powinno.

* PS ~ (tylda) oznacza katalog domowy użytkownika, czyli w moim przypadku /home/daniel

środa, 16 września 2009

Sqlite - wprawka

Zainteresowałem się ostatnio kolejnym tematem, a mianowicie SQL. Do tej pory słyszałem, że to potężne narzędzie do zarządzania bazami danych, ale jakoś nigdy nie wziąłem się za siebie i nie przysiadłem do tego. Ostatnio mam trochę wolnego czasu, więc zajrzałem na stronę: www.w3schools.com gdzie znajduje się wiele materiałów o SQL i to w bardzo przystępnej formie. Po przejściu podstaw miałem okazję wystosować parę zapytań do treningowej bazy danych. Okazało się to łatwiejsze niż przypuszczałem.

Jako bibliotekę obsługującą bazę danych wybrałem sobie Sqlite3. Jest dość nieskomplikowana i nie trzeba stawiać żadnych serwerów SQL czy coś takiego. Instaluje się ją poleceniem:
sudo apt-get install libsqlite3-0 libsqlite3-dev
Bardzo prawdopodobne, że libsqlite3-0 będzie już w systemie gdyż korzysta z niej na przykład Firefox.

Teraz należy stworzyć nowy projekt, dodać do linkera /usr/lib/libsqlite3.so i napisać trochę kodu:
/* 
* File: main.cpp
* Author: daniel
*
* Created on 14 wrzesień 2009, 22:24
*/

#include <stdlib.h>
#include <sqlite3.h>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using std::cout;
using std::endl;

/*
*
*/

//pokazuje wiadomość błędu jeśli istnieje, a jeśli nie to wartość kodu
void handleReturnCode(int returnCode, char* ErrorMsg = NULL) {
if (returnCode != SQLITE_OK) {
if (ErrorMsg != NULL) {
std::cout << ErrorMsg << std::endl;
sqlite3_free(ErrorMsg);
} else {
std::cout << "Return code: " << returnCode << std::endl;
}
}
}

//struktura przechowująca dane o osobie
struct sOsoba {
std::string imie;
std::string nazwisko;
int rokUrodzenia;

sOsoba(const char* p_imie, const char* p_nazwisko, const int p_rokUrodzenia) {
imie = p_imie;
nazwisko = p_nazwisko;
rokUrodzenia = p_rokUrodzenia;
}
};

int main(int argc, char** argv) {
sqlite3* db;
int returnCode;

cout << "Otwieranie bazy danych: " << endl;
returnCode = sqlite3_open("test2.db", &db);
if (returnCode != 0) std::cout << returnCode << std::endl;
char* ErrorMsg;

cout << "Usunięcie tabeli tab" << endl;
returnCode = sqlite3_exec(db, "drop table if exists tab", NULL, NULL, &ErrorMsg);
handleReturnCode(returnCode, ErrorMsg);

cout << "Utworzenie tabeli tab" << endl;
returnCode = sqlite3_exec(db,
"create table tab (imie string, nazwisko string,
rokUrodzenia integer);",
NULL, NULL, &ErrorMsg);
handleReturnCode(returnCode, ErrorMsg);

cout << "Utworzenie listy osob" << endl;
std::vector<sOsoba> listaOsob;
listaOsob.push_back(sOsoba("Donald", "Tusk", 1957));
listaOsob.push_back(sOsoba("Lech", "Kaczyński", 1949));
listaOsob.push_back(sOsoba("Jarosław", "Kaczyński", 1949));
listaOsob.push_back(sOsoba("Grzegorz", "Napieralski", 1974));
listaOsob.push_back(sOsoba("Waldemar", "Pawlak", 1959));
listaOsob.push_back(sOsoba("Grzegorz", "Schetyna", 1963));

//Dodawanie kolejnych nazwisk
std::vector<sOsoba>::iterator it = listaOsob.begin();
while (it != listaOsob.end()) {
cout << "Dodawanie " << it->imie<< " " << it->nazwisko << endl;
std::stringstream query;
query << "insert into tab values (\""
<< it->imie
<<"\", \""
<< it->nazwisko
<< "\", "
<< it->rokUrodzenia <<");";
returnCode = sqlite3_exec(db, query.str().c_str(), NULL, NULL, &ErrorMsg);
handleReturnCode(returnCode, ErrorMsg);
it++;
}

char** result;
int nRows, nCols;
//zapytanie o polityków którzy urodzili się po 1960
sqlite3_get_table(db,
"select nazwisko, rokUrodzenia
from tab
where rokUrodzenia > 1960
order by rokUrodzenia asc",
&result, &nRows, &nCols, &ErrorMsg);
cout << "Result: [" << nRows << ", " << nCols << "]" << endl;
//zerowy wiersz to nagłówki tabeli, a reszta to wyniki, więc rzędów jest nRows + 1
for (int i = 0; i <= nRows; i++) {
for (int j = 0; j < nCols; j++) {
cout << result[i * nCols + j] << "\t";
if (j > 0 && i>0) {
//dla sprawdzenia czy da się łatwo skonwertować liczbę
int rok = atoi(result[i * nCols + j]);
cout << "\t skonwertowany rok: " << rok;
}
}
cout << endl;
}
//zwalnianie pamięci przydzielonej dla tabeli
sqlite3_free_table(result);

cout << "Zamykanie bazy danych" << endl;
sqlite3_close(db);
//wywołanie polecenia systemowego eksportującego tabelę tab do pliku csv
system("sqlite3 -csv test2.db \"select * from tab;\" > wyjscie.csv");
cout << endl;
//wyświetlenie zawartości pliku csv w konsoli
system("cat wyjscie.csv");

return (EXIT_SUCCESS);
}

czwartek, 23 lipca 2009

OpenCV

OpenCV to prawdopodobnie najbardziej znana biblioteka związana z komputerową "wizją". Chodź od dłuższego czasu nic w niej się nie dzieje, to wydaje się nie tracić na popularności. Miałem okazję zapoznać się z nią na laboratorium przedmiotu Sensoryka i Systemy Wizyjne. Na zajęciach wykorzystywaliśmy Visual C++, a teraz chciałbym móc pracować z tą biblioteką w NetBeans na Ubuntu 8.04. Okazało się, że chęć tę bardzo łatwo przekuć w czyn. Oto co należy zrobić:
Nie zamierzam instalować ostatniej wersji z CVS czy SVN. Chcę wykorzystać to co jest w repozytorium Ubuntu. Mamy tam dostępną wersję 1.0.0. Aby dowiedzieć się jakie są nazwy paczek należy wpisać:
apt-cache search opencv
. W odpowiedzi dostaniemy:
libcv-dev - development files for libcv
libcv1 - computer vision library
libcvaux-dev - development files for libcvaux
libcvaux1 - computer vision extension library
libhighgui-dev - development files for libhighgui
libhighgui1 - computer vision GUI library
opencv-doc - OpenCV documentation and examples
python-opencv - Python bindings for the computer vision library

Nie potrzebujemy dokumentacji, która jest dostępna tutaj (dla wersji 1.0) lub tutaj (dla wersji 1.1). W repozytorium znajduje się wersja 1.0. Wydaje mi się, że nie różni się ona zbytnio od 1.1, ale nie przeglądałem changeloga, więc nie wiem na pewno. W każdym razie dokumentacja jest w internecie, a Pythona nie zamierzam używać.
Zatem instalujemy paczki:
sudo apt-get install libcv1 libcv-dev libcvaux1 libcvaux-dev libhighgui1 libhighgui-dev
W odpowiedzi dostaniemy:
Czytanie list pakietów... Gotowe
Budowanie drzewa zależności
Odczyt informacji o stanie... Gotowe
Zostaną zainstalowane następujące dodatkowe pakiety:

libatk1.0-dev libavcodec-dev libavformat-dev libavutil-dev libcairo2-dev libdc1394-13-dev libexpat1-dev libfontconfig1-dev libfreetype6-dev libglib2.0-dev libgsm1-dev libgtk2.0-dev libice-dev libjpeg62-dev libogg-dev libpango1.0-dev libpixman-1-dev libpng12-dev libpthread-stubs0 libpthread-stubs0-dev libraw1394-dev libsm-dev libtheora-dev libtiff4-dev libtiffxx0c2 libvorbis-dev libx11-dev libxau-dev libxcb-xlib0-dev libxcb1-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxft-dev libxi-dev libxinerama-dev libxrandr-dev libxrender-dev x11proto-composite-dev x11proto-core-dev x11proto-damage-dev x11proto-fixes-dev x11proto-input-dev x11proto-kb-dev x11proto-randr-dev x11proto-render-dev x11proto-xext-dev x11proto-xinerama-dev xtrans-dev zlib1g-dev
Sugerowane pakiety:
libcairo2-doc libglib2.0-doc libgtk2.0-doc imagemagick libpango1.0-doc
libraw1394-doc
Zostaną zainstalowane następujące NOWE pakiety:
libatk1.0-dev libavcodec-dev libavformat-dev libavutil-dev libcairo2-dev libcv-dev libcv1 libcvaux-dev libcvaux1 libdc1394-13-dev libexpat1-dev libfontconfig1-dev libfreetype6-dev libglib2.0-dev libgsm1-dev libgtk2.0-dev libhighgui-dev libhighgui1 libice-dev libjpeg62-dev libogg-dev libpango1.0-dev libpixman-1-dev libpng12-dev libpthread-stubs0 libpthread-stubs0-dev libraw1394-dev libsm-dev libtheora-dev libtiff4-dev libtiffxx0c2 libvorbis-dev libx11-dev libxau-dev libxcb-xlib0-dev libxcb1-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxft-dev libxi-dev libxinerama-dev libxrandr-dev libxrender-dev x11proto-composite-dev x11proto-core-dev x11proto-damage-dev x11proto-fixes-dev x11proto-input-dev x11proto-kb-dev x11proto-randr-dev x11proto-render-dev x11proto-xext-dev x11proto-xinerama-dev xtrans-dev zlib1g-dev
0 aktualizowanych, 59 nowo instalowanych, 0 usuwanych i 0 nieaktualizowanych.
Konieczne pobranie 0B/17,3MB archiwów.
Po tej operacji zostanie dodatkowo użyte 62,9MB miejsca na dysku.
Czy chcesz kontynuować [T/n]?
Odpowiadamy, że oczywiście tak. Jak widać instaluje się mnóstwo innych paczek - z tego co widać jedne służą do obsługi różnych formatów obrazów i filmów, a inne do obsługi interface'u (np libgtk2.0-dev). Gdy ta czynność się zakończy pliki nagłówkowe będą zainstalowane w /usr/include/opencv, a biblioteki w /usr/lib.
Zaczynamy teraz pracę z NetBeans:
Tworzymy nowy projekt typu "C/C++ Application". Nadajemy mu nazwę "openCV Hello World".
Mamy czysty projekt z pojedynczym plikiem main.cpp. Teraz trzeba powiedzieć kompilatorowi, gdzie znajdują się biblioteki, które ma dołączyć do projektu. Należy kliknąć prawym przyciskiem w oknie "Projects" na obecnym projekcie i przejść do jego właściwości (Properties). Po wybraniu "Linker" dodać ścieżkę do bibliotek (Additional Library Directories): /usr/lib oraz biblioteki (Libraries): cv, cvaux, highgui.

Teraz możemy napisać trochę kodu. Oto propozycja:
#include <stdlib.h>
#include "opencv/cv.h"
#include "opencv/cvaux.h"
#include "opencv/highgui.h"

/*
*
*/
int main(int argc, char** argv) {
//tworzenie zmiennej przechowującej obraz i załadowanie obrazu z pliku, 0 = czarno-biały
IplImage* obraz = cvLoadImage("/home/daniel/Obrazy/hello.jpg", 0);
//utworzenie okna nazwanego Hello World
cvNamedWindow("Hello world");
//wyświetlenie obrazu
cvShowImage("Hello world", obraz);
//oczekiwanie na wciśnięcie klawisza, domyślnie 0 = nieskonczonosc
cvWaitKey();
//zamknięcie okna, można użyć cvDestroyAllWindows()
cvDestroyWindow("Hello world");
//zwalniamy pamięć zajętą przez zmienną obraz
cvReleaseImage(&obraz);
return (EXIT_SUCCESS);
}
Efekt po wciśnięciu F6:

niedziela, 19 lipca 2009

LaTeX

Przeglądałem dziś sobie jakie programy są dostępne przez "Dodaj/Usuń programy" w Ubuntu i w oczy rzucił mi się Texmaker. Zainstalowałem go i próbowałem stworzyć prosty dokument. Skonfigurowałem sobie opcję "Quick build", aby po naciśnięciu F1 otrzymać gotowy plik PDF. Niestety dostawałem błędy w stylu "błąd podczas wykonywania polecenia", "nie istnieje plik dziennika (log file)". Swego czasu próbowałem już skompilować dokument na Windowsie i poległem. Za to w trakcie prób rozwiązania problemu natrafiałem na instrukcję dla Linuksów. Było tam wspomniane o poleceniach typu pdflatex. Sprawdziłem w terminalu czy mam coś takiego zainstalowanego. Okazało się, że nie, więc trzeba je doinstalować. Tylko jak się nazywa ta paczka. Poszukiwania (apt-cache search latex | less) doprowadziły do tego, że w oczy rzuciła mi się duża ilość paczek z "texlive" na początku. Tylko jak będzie się nazywać główny pakiet? Pewnie texlive. Polecenie apt-cache show texlive upewniło mnie, że zgadłem:
This metapackage provides a decent selection of the TeX Live packages which should suffice for the most common tasks.
Zainstalowałem zatem paczkę texlive wraz z texlive-lang-pl, gdyż uznałem, że ta druga może się przydać. Po tym wszystkim odinstalowałem i zainstalowałem ponownie Texmakera.
Teraz ponownie skonfigurowałem Quick Build, żeby tworzyło pdfa. Następnie przy wykorzystaniu "Quick Wizard" stworzyłem następujący dokument:
\documentclass[10pt,a4paper]{article}
\usepackage[latin2]{inputenc}
\author{Imię Nazwisko}
\title{O pisaniu w LaTeXu}
\begin{document}
Treść dokumentu
\end{document}
Zapisałem do pliku /home/daniel/Dokumenty/LaTeX/pierwszy. To był mój pierwszy błąd- pliki należy zapisywać koniecznie z rozszerzeniem .tex. Udało mi się skompilować tekst do pdfa, ale zamiast ź otrzymałem jakieś L z zawijasami. Zacząłem kombinować z różnymi paczkami:
\usepackage[polish]{babel}
\usepackage[]{polski}
\usepackage[OT4]{polski}
\usepackage[T1]{polski}
\usepackege[MeX]{polski}
Za każdym razem dostawałem inny efekt. A to zamiast ź było ij, a to w ogóle ź zostało pominięte. Przez pewien czas pomyślałem, że może brakuje mi czcionek, ale wersja zapisu ź z akcentem działała (wpisanie \'z również powoduje wyświetlenie ź).
Wtedy trafiłem na stronę morony.pl, gdzie opisywano instalację paczek dla Windows. Wykorzystałem przykładowy plik LaTeXa:
\documentclass[11pt,a4paper]{article}
\usepackage{polski}
\usepackage[cp1250]{inputenc}
\title{\LaTeX}
\author{Marcin Polkowski}
\begin{document}
\maketitle
\section{Matematyka}
$$f(x)=\left\{
\begin{array}{ccc}
\sin{x}&\mbox{dla}&x<0\\ 0&\mbox{dla}&x=0\\ \cos{x}&\mbox{dla}&x>0
\end{array}
\right.$$
\section{Polskie krzaczki}
Późną nocą grań olśniła księżyca jasność.
Pchnąć w tę łódź jeża lub ośm skrzyń fig.
Różowy słoń ma usiąść na tępych gwoździach
\end{document}
Jako, że używam Ubuntu zmieniłem windowsowe kodowanie cp1250 na latin2. Nikt nie potrafi wyobrazić sobie mojego zdziwienia, gdy powyższy tekst skompilował się do pięknego pdfa. Na razie służy mi jako szablon do tworzenia kolejnych dokumentów. Może jak kiedyś zrozumiem na czym polega ustawianie opcji poszczególnych paczek to będę pisał preambułę sam. Na razie jest to dla mnie czarna magia i to rozwiązanie jest wystarczająco dobre.

Taka drobnostka, co do której nie jestem pewien czy ma znaczenie: w Options->Configure Texmaker->Editor ustawiłem kodowanie edytora na ISO-8859-2

Pomocą służyły mi dwa główne opracowania:
Nie za krótkie wprowadzenie do systemu LaTeX oraz Jak szybko zacząć pisać w LaTeX Pomocna również była strona Grupy Użytkowników Systemu Tex (w skrócie GUST).

poniedziałek, 13 lipca 2009

NetBeans i C/C++

Jako że będę niedługo potrzebował umiejętności pisania programów w C z Linuksem jako docelową platformą postanowiłem zrobić coś w tym kierunku. Korzystać będę z Ubuntu 8.04 LTS.
Rozpocząłem od przejrzenia książki Beginning Linux Programming z której to dowiedziałem się jak kompilować programy z pomocą linii komend. Zdaję sobie sprawę, że niektórzy to lubią i ma to "oldskulowy" urok, ale jednak cenię sobie wyżej wygodę niż bajery.
Swego czasu dużo dobrego słyszałem o NetBeans i postanowiłem je wypróbować. Jest to środowisko programistyczne, które można wykorzystać do programowania w wielu językach (pierwotnie była to chyba Java). Mnie interesowała wersja dla C/C++, toteż ściągnąłem odpowiedni instalator ze strony http://www.netbeans.org/downloads/index.html. Na stronie dostępne są też tutoriale dotyczące obsługi tego środowiska.
Instalację przeprowadzałem już jakiś czas temu, więc nie pamiętam wszystkich szczegółów, ale wydaje mi się, że brakowało tam kompilatora C++ - wystarczy doinstalować g++ na przykład za pomocą Synaptica i w Narzędzia=>Opcje=>C/C++=>Build Tools w okienku C++ Compiler: wpisać /usr/bin/g++. Od teraz można już działać bez przeszkód.
Moje dotychczasowe doświadczenia z programowaniem w C++ to głównie 2 programy: MS Visual Studio i C++ Builder z Borlanda. Ten drugi to już zabytek, więc nie ma co porównywać. Jak na tak stary program jest bardzo wygodny, ale znalezienie jakiejś instalki w internecie, a do niej jeszcze klucza rejestracyjnego to już nie jest zabawne. Acha, żeby działał debugger należy się zarejestrować. Swojego czasu ominąłem rejestrację i zachodziłem w głowę dlaczego debugger, który kiedyś działał bez problemu, teraz w ogóle nie chce tego robić. Wracając jednak do sedna - porównanie będzie odbywać się między VS, a NetBeans.
Jest kilka funkcji, które w IDE są przeze mnie wymagane. Działający debugger to podstawa - byłem bardzo mile zaskoczony, gdy zobaczyłem w NetBeans, że mogę bez problemu włączyć uruchomienie programu linia po linii wraz z poglądem zmiennych, dodawanie watchów itp. Bardzo miłe zaskoczenie.
Pisząc program bardzo pomocne są podpowiedzi nazw zmiennych i funkcji. Zarówno w VS, jak i NB mamy je dostępne pod ctrl+spacja, ale w tym drugim mamy to bardziej rozbudowane - podpowiadane są też nazwy plików nagłówkowych, dyrektywy preprocesora, rozwinięcia kodu (napisz "for", naciśnij ctrl+spacja, a dostaniesz podpowiedź "fori", które po wybraniu rozwija się w pętlę for ze zmienną i. To jeszcze nie koniec - zmienna i jest w polu, które uaktywnia się po rozwinięciu. Możemy wpisać dowolną nazwę zmiennej, a zastąpi ona i we wszystkich miejscach w pętli. Coś takiego dostępne w VS jest chyba tylko wtedy gdy piszemy w C# (albo może wszystko pisane przy użyciu CLR ma taką właściwość). Nie udało mi się za to znaleźć jak włączyć podpowiedź listy parametrów w funkcji, czyli odpowiednik Visualowego ctrl+shift+spacja, ale to pewnie jeszcze gdzieś to się przede mną ukrywa.
Co jeszcze mi się podoba w NB, a nie widziałem w VS to ilustracja czasu wykonywania programu i zużycia pamięci. Wciskamy F6, aby uruchomić nasz projekt i widzimy jak rośnie czy spada rozmiar naszego programu. Inny przykład to kombinacja alt+shift+f, która ładnie formatuje kod - wstawia spację między operatorami, wyrównuje wszystko, generalnie sprawia, że kod staje się czytelny.
Dodatkowo mamy integrację z CVS, SVN, Kenai i nie wiem nawet czym jeszcze. Trochę czasu minie zanim odkryję wszystkie funkcje. Wrażenia z użytkowania są niezmiernie zadowalające - obecnie staram się rozwiązać jak najwięcej zadań z Projektu Euler o którym napiszę więcej wkrótce.