ورود به حساب کاربری

نام کاربری *
رمز عبور *
مرا به خاطر بسپار.

بنیاد توسعه رایانش سریع و ابری

HPC and Cloud Computing Development Foundation

آشنايي با معماري، نصب و شروع كار با CUDA

کمتر برنامه‌نويسي وجود دارد که چيزي از کودا نشنيده باشد. معماري برنامه‌نويسي موازي عرضه‌شده توسط Nvidia از بدو عرضه سر و صداي بسياري در دنياي برنامه‌نويسي GPU (و به‌تازگي CPU) به پا کرده است. البته مانند هر موضوع جديدي برخي از اين سر‌و‌صداها بيشتر حالت تبليغاتي داشته و بعضي از آن‌ها نيز به سندروم تب فناوري‌هاي جديد برمي‌گردد. به هر ترتيب، جدا از تمام بحث هاي «زرد»، کودا پلتفرمي بسيار قدرتمند است که مي‌تواند لبخند را بر لب هر برنامه‌نويسي بنشاند. يادگيري كامل کودا در يک مقاله امري غيرممکن است، اما در اين مقاله سعي بر آن بوده كه تا حد ممكن مقدمات اين معماري بيان شود و تصوير‌كلي براي توسعه‌دهندگان علاقه‌مندترسيم شود. در كادر انتهايي مطلب حاضر روش نصب كودا نيز توضيح داده شده است. 

مقدمه
مزاياي برنامه‌نويسي روي GPU موضوعي نيست که به توضيح نياز داشته باشد. اين که مي‌توان با استفاده از پردازنده‌هاي گرافيکي، سرعت بعضي از برنامه‌ها را تا ده‌ها برابر افزايش داد، در يك كلام، فوق‌العاده است. با اين حال، تا مدت‌ها اين مزيت به‌وسيله يک مانع بزرگ محدود شد. اين مانع در واقع شيوه برنامه‌نويسي براي GPU‌ها بود. برنامه‌نويساني که با زبان‌هايي مانند C، C++ ، جاوا، زبان‌هاي دات‌نتي، پايتون و بسياري از زبان‌هاي برنامه‌نويسي معمول ديگر آشنا بودند، به‌طور طبيعي علاقه‌اي به يادگيري زبان يا پلتفرم جديدي نداشتند. آن‌ها ترجيح مي‌دانند که بتوانند به وسيله همين زبان‌ها براي پردازنده‌هاي گرافيکي نيز برنامه نويسي كنند.


اين دقيقاً همان چيزي است که کودا را محبوب کرده است. با استفاده از اين معماري شما مي‌توانيد برنامه‌ خود را با زبان C نوشته و سپس روي پردازنده گرافيکي اجرا كرده و از سرعت اجراي آن لذت ببريد. مورد مهم ديگر وجود پلتفرمي بود که بتواند روي دستگاه‌هاي مختلف اجرا شود. کودا با اين شعار که مي‌تواند براي شما سطحي قابل‌قبول از کارايي و مقياس‌پذيري را در يک زمان به ارمغان آورد، وارد بازار برنامه‌نويسي شد. درباره اين معماري گفته مي‌شود‌: «کودا معماري‌اي است که به جاي محدود‌کردن شما توسط کارايي يک سري کتابخانه، اجازه مي‌دهد کار مورد نظرتان را انجام دهيد.» با وجود اين‌که کودا روي تراشه‌هاي گرافيكي اجرا مي‌شود، در واقع برنامه‌نويسي کودا با برنامه‌نويسي GPGPU تفاوت دارد. درگذشته، نوشتن نرم‌افزار براي GPU به اين معني بود که به زبان GPU برنامه بنويسيد. در مقابل، همان‌طور که عنوان شد، کودا به شما اجازه مي‌دهد با زبان‌هاي معمول برنامه‌اي بنويسيد که مي‌تواند روي GPU نيز اجرا شود. همچنين به دليل آن‌که کودا مي‌تواند نرم‌افزار شما را به صورت مستقيم روي سخت افزار گرافيکي کامپايل کند، کارايي به دست آمده نيز افزايش پيدا مي‌کند.


اما مزيت اصلي کودا چيست؟ به طور کلي، مهم‌ترين فايده‌اي که استفاده از پردازنده‌هاي گرافيکي براي توسعه‌دهنده در پي دارد، توانايي اجراي رشته‌هاي پردازشي بسيار زياد در يک زمان است. به اين ترتيب، اگر برنامه شما به گونه‌اي باشد که از Task‌هاي بسيار زياد و سبک تشکيل شده باشد، يعني تعداد Task‌ها بسيار بالا، اما ميزان نياز آن‌ها به پردازنده کم باشد، کودا مي‌تواند عملکردي خيره‌کننده را براي شما به ارمغان بياورد. البته توانايي‌هاي کارت‌هاي گرافيکي روز به روز در حال افزايش است. به عنوان مثال، بورد‌هاي جديد شامل پهناي باند حافظه بالاتر، انتقال داده غيرهمزمان، عمليات اتميک و محاسبات Floating Point نيز مي‌شوند که مي‌تواند دست برنامه‌نويس را بازتر كند.در بخش بعدي به بررسي اصول برنامه نويسي کودا مي‌پردازيم. سپس دو مثال عملي را با يکديگر مرور خواهيم کرد و در نهايت نحوه نصب کودا روي اوبونتو و کامپايل و اجراي برنامه‌ها را بيان خواهيم کرد.

برنامه نويسي با کود‌ا
 مقدمات
قبل از شروع برنامه‌نويسي براي کودا بايد با چند موضوع آشنا شويم‌:
- هر برنامه کودا در واقع برنامه سريالي است که شامل هسته‌هاي موازي مي‌شود.
- کد سريال زبان C در درون رشته‌هاي پردازشي ميزبان (يا همان Thread‌هاي CPU) اجرا مي‌شود.
- هسته موازي کد C در درون تعداد زيادي از رشته‌هاي پردازشي دستگاه کودا (يا همان رشته‌هاي پردازشي GPU) اجرا مي‌شوند.

#include <stdio.h>
#include <assert.h>
#include <cuda.h>
void incrementArrayOnHost(float *a, int N)
{
  int i;
  for (i=0; i < N; i++) a[i] = a[i]+1.f;
}
__global__ void incrementArrayOnDevice(float *a, int N)
{
  int idx = blockIdx.x*blockDim.x + threadIdx.x;
  if (idx<N) a[idx] = a[idx]+1.f;
}
int main(void)
{
  float *a_h, *b_h;           // pointers to host memory
  float *a_d;                 // pointer to device memory
  int i, N = 10;
  size_t size = N*sizeof(float);
  // allocate arrays on host
  a_h = (float *)malloc(size);
  b_h = (float *)malloc(size);
  // allocate array on device
  cudaMalloc((void **) &a_d, size);
  // initialization of host data
  for (i=0; i<N; i++) a_h[i] = (float)i;
  // copy data from host to device
  cudaMemcpy(a_d, a_h, sizeof(float)*N, cudaMemcpyHostToDevice);
  // do calculation on host
  incrementArrayOnHost(a_h, N);
  // do calculation on device:
  // Part 1 of 2. Compute execution configuration
  int blockSize = 4;
  int nBlocks = N/blockSize + (N%blockSize == 0?0:1);
  // Part 2 of 2. Call incrementArrayOnDevice kernel
  incrementArrayOnDevice <<< nBlocks, blockSize >>> (a_d, N);
  // Retrieve result from device and store in b_h
  cudaMemcpy(b_h, a_d, sizeof(float)*N, cudaMemcpyDeviceToHost);
  // check results
  for (i=0; i<N; i++) assert(a_h[i] == b_h[i]);
  // cleanup
  free(a_h); 
  free(b_h);
  cudaFree(a_d);
  }

فهرست 1

 

هسته در‌واقع به معناي تعداد زيادي از رشته‌هاي پردازشي همروند است. در هر زمان يک هسته روي دستگاه اجرا مي‌شود. نکته اصلي اينجا است که هر هسته توسط تعداد زيادي از رشته‌هاي پردازشي اجرا مي‌شود. به اين ترتيب، همه رشته‌هاي پردازشي در حال اجراي يک کد هستند، اما داده‌هاي مربوط به هر کدام متفاوت است. خود رشته‌هاي پردازشي در داخل بخش‌هاي بزرگ‌تري به نام بلاک‌هاي  رشته‌پردازشي قرار مي‌گيرند. هر هسته در واقع گريدي از بلاک‌هاي  رشته‌پردازشي است. اين تعريف‌ها ممکن است براي شروع کار کمي گيج‌کننده به نظر برسند. اما نگران نباشيد! در ادامه متن توضيح دقيق‌تري را براي آن‌ها ارائه خواهيم كرد.


بلاک‌هاي   رشته‌پردازشي نمي‌توانند با يکديگر همزمان‌سازي شوند. به اين ترتيب، آن‌ها مي‌توانند به هر ترتيبي اجرا شوند‌: چه سريال و چه موازي. حال مي‌توان فلسفه ايجاد بلاک را درک كرد. هر بلاک در يک زمان به يک پردازنده (يا يک هسته پردازنده) نسبت داده‌مي‌شود. پس اگر پردازنده ما چهار هسته داشته باشد، در هر زمان مي‌تواند چهار بلاک از رشته‌هاي پردازشي را با يکديگر اجرا كند.دركادر«توابع مفاهيم اوليه»بعضي از مفاهيم اوليه در کودا رامشاهده مي‌كنيد‌. روتين‌هاي زمان اجراي کودا درباره روتين‌هاي زمان اجراي كودا به جدول‌هاي 1 و 2 مراجعه كنيد.

 

جدول1

 

جدول 2


شروع کدنويسي
در قسمت بالاي کدموجود در فهرست1 تابع IncrementArrayOnHost را مشاهده مي‌کنيد که با طي کردن يک حلقه روي آرايه، هر عنصر آن را به اندازه يک واحد افزايش مي‌دهد. همان‌طور که از کد اين تابع مشخص است، هيچ استفاده‌اي از امکانات کودا در پياده‌سازي آن انجام نشده است. از اين تابع در انتها براي بررسي کردن درستي کارکرد معادل کودايي آن استفاده مي‌شود. تابع بعدي، يعني IncrementArrayOnDevice يک هسته  يا كرنل کودا محسوب مي‌شود و به همين دليل نيز از __Global__ در تعريف آن استفاده شده است. هسته‌هاي کودا بايد داراي نوع بازگشتي void بوده و تنها از طريق ميزبان(host) فراخواني ‌شوند. کاري که اين هسته انجام مي‌دهد درست همان کاري است که تابع IncrementArrayOnHost انجام مي‌داد. با مشاهده کد متوجه خواهيد شد که اين هسته در واقع هيچ حلقه‌اي را در درون خود ندارد. دليل اين امر نيز مشخص است. از آنجا که اين کد توسط تعداد زيادي   رشته‌پردازشي اجرا مي‌شود، هر کدام از اين رشته‌هاي پردازشي عمليات اضافه‌کردن يک به خانه‌هاي آرايه را براي يک خانه خاص آرايه انجام داده و در نهايت، نتيجه نهايي از تلفيق کار آن‌ها به دست مي‌آيد. هر هسته‌اي که روي يک دستگاه کودا اجرا مي‌شود، تعدادي متغير دروني دارد که از روي تنظيمات اجراي هسته تنظيم مي‌شوند. اين متغيرها عبارتند از‌:
- BlockIdx: که انديس بلاک در داخل گريد را نشان مي‌دهد.
- ThreadIdx: که انديس رشته‌پردازشي در درون بلاک را نشان مي‌دهد.
- BlockDim: که تعداد رشته‌هاي پردازشي داخل بلاک را نشان مي‌دهد.


اين‌ها در واقع Structure‌ يا ساختارهايي هستند که شامل مقادير Integer براي متغيرها مي‌شوند. به عنوان مثال، بلاک‌ها به دليل ساختارهاي سه‌بعدي، سه مؤلفه x ، y و z دارند. در مقابل گريدها به دليل دو بعدي بودن دو مؤلفه x و y دارند. در اين مثال با توجه به اين‌که آرايه به صورت يک بعدي وارد دستگاه کودا شده است، تنها از x (يعني يک بعد) استفاده کرده است. به اين ترتيب، براي محاسبه انديس   رشته‌پردازشي يا همان idx مي‌توان از اين روش استفاده کرد‌:


int idx = blockIdx.x * blockDim.x + threadIdx.x;


به‌اين‌ترتيب، ابتدا مشخص مي‌شود که شروع بلاک از چه رشته پردازشي است، سپس به اندازه انديس   رشته‌پردازشي در آن بلاک جلو مي‌رويم. در نوشتن کد اين هسته نکته ديگري نيز وجود دارد. از آنجا که ممکن است تعداد رشته‌هاي پردازشي توليد شده بيشتر از اندازه آرايه باشد، بايد انديس به دست آمده براي   رشته‌پردازشي بررسي شود تا بزرگ‌تر از اندازه آرايه نباشد. در صورتي که اين شرط درباره   رشته‌پردازشي صدق نكند، به‌طور مشخص آن   رشته‌پردازشي خاص نبايد کاري را انجام دهد (يعني نبايد عمليات اضافه کردن يک را اجرا کند). اين موضوع به وسيله شرط if ذکر شده در بدنه هسته پياده‌سازي شده است.حال به اجراي متد Main بازگشته و کار را از آنجا دنبال مي‌کنيم.  خطوط اوليه Main عموماً به تعريف متغيرها و مقداردهي اوليه آن‌ها بر‌مي‌گردد. دستوري که مي‌تواند در اين ميان مورد توجه قرار گيرد، CudaMalloc و CudaMemcpy است. اولي مانند دستور Malloc در C عمل مي‌کند و حافظه‌اي را براي متغير مشخص شده به عنوان پارامتر در نظر مي‌گيرد. دومي نيز براي کپي‌کردن حافظه‌اي از ميزبان به دستگاه کودا و برعكس به کار مي‌رود. در نخستين بار فراخواني CudaMemcpy مقدار متغير a_h از ميزبان به متغير a_d از دستگاه کودا کپي‌مي‌شود. بعد از اين دستور نيز تابع IncrementArrayOnHost فراخواني مي‌شود.


از اين مرحله به بعد وارد فاز «کودا محور» کد‌ مي‌شويم. قبل از ورود به اين بخش بايد ابتدا با دو متغير BlockSize و NBlocks آشنا شويم. اين دو متغير براي تعريف تنظيمات اجرا يا Execution Configuration به کار مي‌روند. متغير NBlocks بيان کننده تعداد بلاک‌ها در يک گريد و متغير BlockSize نشان‌دهنده تعداد رشته‌هاي پردازشي در يک بلاک است. در اين ميان مقداردهي NBlocks در کد حاوي نکته‌اي ظريف است:


int nBlocks = N/blockSize + (N%blockSize == 0?0:1);


در حالتي که N قابل تقسيم بر BlockSize نباشد، عبارت آخر اين تعريف يک بلاک ديگر به گريد اضافه مي‌کند. از طرفي اين موضوع باعث مي‌شود که رشته‌هاي پردازشي‌اي در بلاک اضافه شده به وجود آيند که هيچ کاري را انجام‌نمي‌دهند. اين رشته‌هاي پردازشي به وسيله if ذکر شده در تعريف IncrementArrayOnDevice شناسايي شده و در عمليات اضافه کردن يك به خانه‌هاي آرايه شرکت داده نمي‌شوند. البته، توجه به اين نکته نيز الزامي است که در اين مثال، براي سادگي فرض شده که اندازه آرايه کوچک‌تر از تعداد رشته‌هاي پردازشي است که مي‌توانند در چهار بلاک قرار گيرند.


دستور بعدي در واقع همان جايي است که توليد رشته‌هاي پردازشي را انجام مي‌دهد‌:


incrementArrayOnDevice <<< nBlocks, blockSize >>> (a_d, N);


اين دستور هسته IncrementArrayOnDevice را براي تعداد NBlocks بلاک را که هر کدام شامل رشته‌هاي پردازشي به تعداد BlockSize  هستند، فراخواني مي‌کند. در ادامه نيز آرگومان‌هاي مورد نظر براي هسته در داخل پرانتز قرار داده مي‌شوند که در اينجا a_d و N است. پس از اين دستور بلافاصله هسته با توجه به تنظيمات اجراي ذکر شده، اجرا مي‌شود. ميزبان نيز شروع به اجراي خط بعدي مي‌کند و اين يعني که در اين نقطه هم ميزبان و هم دستگاه کودا به صورت همزمان در حال کار هستند. ميزبان دستور CudaMemcpy را اجرا مي‌کند. اما اجراي اين دستور تا زماني که تمام رشته‌هاي پردازشي کارشان را روي دستگاه کودا به پايان برسانند، متوقف مي‌ماند. پس از پايان يافتن کار رشته‌هاي پردازشي مقدار a_d به متغيري روي حافظه ميزبان باز‌مي‌گردد (b_h). در پايان نيز مقدار به دست آمده توسط روتين IncrementArrayOnHost با مقدار به دست آمده توسط هسته IncrementArrayOnDevice چک شده و يکسان بودن آن‌ها تأييد مي‌شود. شکل 1 روند اجراي برنامه را به صورت بصري نشان مي‌دهد.

شکل 1 - روند اجراي برنامه Increment Array به صورت بصري


کنترل خطا به وسيله کودا

کنترل خطا براي هيچ برنامه‌نويسي لذت بخش نيست. هر چند که اين موضوع علاوه بر کمک به کاربر نرم‌افزار مي‌تواند در روند اشكال‌زدايي برنامه به درد توسعه دهنده نيز بخورد، با اين حال انجام کاري تکراري، زمان بر و در حقيقت غيرجذاب! موردي است که بسياري از برنامه نويسان به آن علاقه‌اي ندارند. با اين حال، به خصوص در مواردي که شما در حال نوشتن يک برنامه تجاري هستيد، استفاده از يک سياست کنترل خطاي خوب مي‌تواند برنامه شما را حرفه‌اي‌‌تر سازد.کنترل خطا در کودا نيز فراموش نشده است. هر فراخواني مربوط به کودا (به استثناي اجراي هسته‌ها) يک کدخطا از نوع CudaError_t را باز‌مي‌گرداند. اگر عمليات موفقيت آميز باشد، CudaSuccess بازگردانده شده و در غير اين صورت کدخطا برمي‌گردد. تابع دريافت خطا مي‌تواند به اين صورت تعريف شود:


char *cudaGetErrorString(cudaError_t code);


کودا همچنين متدي با نام CudaGetLastError فراهم مي‌آورد که آخرين خطايي را که در تمام فراخواني‌هاي زمان اجراي   رشته‌پردازشي ميزبان رخ داده است،‌ گزارش مي‌کند. حال نحوه استفاده از اين دستورها را توضيح مي‌دهيم. ابتدا توجه داشته باشيد که ماهيت اجراي ناهمزمان هسته باعث مي‌شود تا نتوان به وسيله CudaGetLastError خطاها را شناسايي کرد. به‌جاي اين کار بايد با استفاده از دستور CudaThreadSynchronize منتظر ماند تا تمام فراخواني‌ها کامل شوند. در واقع، به دليل فراخواني چندگانه هسته‌ها، کنترل خطا نمي‌تواند زودتر از اين نقطه به انجام رسد. مگر اين‌که خود برنامه نويس مکانيزم کنترل خطايي را در درون تابع هسته تعبيه کرده باشد.براي کنترل بهتر خطاها و تبديل آن‌ها به پيام‌هاي قابل فهم مي‌توان به اين صورت عمل كرد که در ابتدا يک تابع خاص براي کنترل خطا تعريف کرد. مانند‌:


void checkCUDAError(const char *msg)
{
    cudaError_t err = cudaGetLastError();
    if( cudaSuccess != err)
    {
        fprintf(stderr, «Cuda error: %s: %s.\n», msg,
                                  cudaGetErrorString( err) );
        exit(EXIT_FAILURE);
    }

}

اين تابع ابتدا کد آخرين خطاي رخ داده را ذخيره‌کرده و سپس در صورتي که اين مقدار برابر CudaSuccess نبوده يا به بيان ديگر همه چيز به درستي پيش نرفته باشد، پيغام مناسب را با استفاده از تابع CudaGetErrorString چاپ مي‌کند. حال مي‌توان در خلال برنامه وبعد از هر فراخواني توابع کودا اين تابع را نيز فراخواني کرد. به اين ترتيب، بلافاصله پس از رخ‌دادن خطا، کاربر از آن مطلع مي‌شود.

 

حافظه‌هاي global و shared
استفاده درست از حافظه‌هاي global  (عمومي) و shared  (اشتراکي) مي‌تواند کارايي برنامه شما را تا ده‌ها برابر افزايش دهد. به همين دليل هر برنامه نويسي که با کودا سر و کار دارد بايد با اين مفاهيم به طور کامل آشنا باشد. فضاهاي حافظه عمومي و اشتراکي Cache نمي‌شوند. يعني هر دسترسي به اين دو فضا، به صورت صريح انجام مي‌شود. به همين دليل، دانستن هزينه خواندن و نوشتن روي اين دو فضا و مقايسه آن‌ها با يکديگر ضروري است. دسترسي به حافظه‌هاي عمومي در عمل چيزي نزديک به صد تا 150 برابر کندتر از دسترسي به حافظه‌هاي اشتراکي است. اين همان چيزي است که باعث مي‌شود تا توسعه‌دهندگان ترجيح دهند، استفاده از حافظه‌هاي عمومي را به حداقل رسانده و تا جاي ممکن حتي به قيمت کپي‌کردن‌هاي متعدد، از حافظه‌هاي اشتراکي استفاده كنند. با اين حال، استفاده از رجيسترها که تنها توسط خود رشته پردازشي قابل دسترس هستند، از حافظه هاي اشتراکي نيز سريع‌تر است.ميزان حافظه اشتراکي را به صورت زمان اجرا و از طريق ميزبان مي‌توان مشخص كرد. اين کار در فراخواني هسته به اين صورت انجام مي‌شود:


Kernel-Func<<< dimGrid, dimBlock, sharedMemSize >>>( ..);


به اين ترتيب، مي‌توان در بدنه هسته متغير اشتراکي بدون ذکر صريح اندازه تعريف كرد. به عنوان مثال:


extern __shared__ int s_data[];


بايد توجه داشت متغيرهايي که به اين صورت تعريف مي‌شوند، همگي از يک آدرس در حافظه شروع مي‌شوند. پس بايد توجه داشته باشيد که در صورت استفاده از چندين متغير بايد Offset مربوط به آن‌ها را به صورت دستي تنظيم کنيد. به عنوان مثال، فرض کنيد مي‌خواهيم جايگزين دايناميکي براي آرايه‌هاي زير بيابيم.


short a[128];   float b[64];    int c[256];
براي انجام اين کار بايد به اين ترتيب عمل كنيم:
extern __shared__ char array[];
short* a=(short*)array;
float* b=(float*)&a[64];
int* c=(int*)&b[128];


برخي امکانات ديگر
يک دسته از توابعي که در سخت افزارهاي جديدتر کودا محور تعبيه‌شده‌اند، توابع مربوط به انجام عمليات صحيح اتميک در‌حافظه عمومي‌هستند. توابعي مانند AtomicAdd، AtomicMin و AtomicAnd. به عنوان مثال، AtomicAdd() يک کلمه 32 بيتي يا 64 بيتي را از حافظه عمومي يا اشتراکي خوانده ، يک عدد صحيح را با آن جمع مي‌کند و حاصل را دوباره در همان آدرس ذخيره مي‌کند. تمام اين عمليات نيز به صورت اتميک صورت مي‌پذيرد. به اين معنا که هيچ رشته‌پردازشي ديگري هنگام انجام اين کار نمي‌تواند به اين آدرس دسترسي داشته باشد.


همچنين کودا از تعداد قابل توجهي از کتابخانه‌هاي بهينه شده بهره مي‌برد که مي‌توانند کارايي برنامه شما را افزايش دهند. BLAS (سرنام Basic Linear Algebra Subprograms‌) يك پکيج يا در واقع يك رابط برنامه‌نويسي براي انجام عمليات پايه‌اي جبرخطي از قبيل ضرب ماتريس‌ها و بردارها است. NVidia اين رابط را به وسيله کتابخانه خودش براي GPU با نام CUBLAS پشتيباني مي‌کند. يکي ديگر از پکيج‌هاي کاربردي CUFFT، NVIDIA ‌‌است که يک پياده‌سازي GPU محور را براي تبديل سري فوريه فراهم مي‌آورد. متدي معمول و پرکاربرد در کاربردهاي علمي و پردازش سيگنال. اين پکيج از روي FFTW که يک پکيج بسيار بهينه شده براي پردازنده‌هاي همه منظوره است، نوشته شده است. علاوه بر اين پکيج‌هاي مختلفي در رابطه با مسائل محاسباتي مربوط به Finance براي کودا وجود دارد. به دليل ماهيت محاسباتي صرف اين مباحث، انجام کارهايي مثل قيمت‌گذاري سهام و مسائل مربوط به مدل بلک‌شولز از جمله کاربردهايي هستند که با ماهيت طراحي کودا بسيار هماهنگ هستند.

 

منبع : ماهنامه شبکه