Видеокарты
|
Модель
|
Гигафлопс
|
GeForce
GTX 690
|
5622
|
GeForce
GTX 580
|
1581.056
|
GeForce
GTX 480
|
1344.96
|
GeForce
GTX 280
|
933.120
|
GeForce
9800 GTX
|
648
|
GeForce
8800 GTS 640mb
|
346
|
Таблица 1. Сравнение производительности
видеокарт
Процессоры
|
Модель
|
Гигафлопс
|
AMD Phenom II X6 1100T Black Edition 3.3 ГГц
|
60.1
|
Intel
Core i7-950 3.03 ГГц
|
53.28
|
Intel Core 2 Quad Q8300 2.5 ГГц
|
40
|
AMD AMD ATHLON II X4 645 3.1 ГГц
|
38.44
|
Intel
Core 2 Duo 2.4 ГГц
|
19.02
|
AMD
Athlon 64 X2 4200+ 2.2 ГГц
|
13.02
|
Таблица 2. Сравнение производительности процессоров
Как видно из таблиц, теоретическая
производительность моей видеокарты в 17 раз превосходит производительность
моего процессора.
Для реализации своей программы я использовал
Visual Studio 2010, и написал консольное приложение.
Код программы суммирование вектора GPU
#ifndef _COMMON_H
#define _COMMON_H
#define VECTOR_SIZE 8192
#define BLOCK_SIZE 512
#define BLOCKS (VECTOR_SIZE /
BLOCK_SIZE)"C" void reductionGPU(int *d_Dst, int *d_Src);
#endif
#include <cutil_inline.h>
#include "common.h"
__global__ void reductionKernel(int
*d_Dst, int *d_Src)
{
__shared__ int data[BLOCK_SIZE]; tid
= threadIdx.x; idx = blockIdx.x * blockDim.x + threadIdx.x;
// Копируем из глобальной в локальную блоками
data[tid] = (idx < VECTOR_SIZE) ?
d_Src[idx] : 0;
// Ждём пока каждый поток в блоке скопирует
данные
__syncthreads ();
// Суммирование
в
паралели(
int s = blockDim.x / 2; s > 0; s >>= 1 )
{( tid < s ) [tid] += data[tid +
s];
__syncthreads ();
}
// Первый поток в блоке записывает результат
суммирования
if(tid == 0) _Dst[blockIdx.x] = data[0];
}
extern "C" void
reductionGPU(int *d_Dst, int *d_Src)
{<<<BLOCKS,
BLOCK_SIZE>>>(d_Dst, d_Src);("convolutionRowsKernel() execution
failed\n");
}
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <string.h>
#include <cutil_inline.h>
#include "common.h"
////////////////////////////////////////////////////////////////////////////////
// Main program
////////////////////////////////////////////////////////////////////////////////main(int
argc, char **argv)
{
//Use command-line specified CUDA
device, otherwise use device with highest Gflops/s( cutCheckCmdLineFlag(argc,
(const char **)argv, "device") )(argc, argv);(
cutGetMaxGflopsDeviceId() );
int *d_Input, *d_Output;* h_Input =
new int[VECTOR_SIZE];* h_Output = new int[BLOCKS];
(int i = 0; i < VECTOR_SIZE; ++i)
{_Input[i] = 1;
}
std::cout << "Size of
data: " << VECTOR_SIZE << "\n";::cout <<
"Blocks: " << BLOCKS << "\n";::cout <<
"Allocating and initializing CUDA arrays...\n";( cudaMalloc((void
**)&d_Input, VECTOR_SIZE * sizeof(int)) );( cudaMalloc((void
**)&d_Output, BLOCKS * sizeof(int)) );( cudaMemset(d_Output, 0x00, BLOCKS *
sizeof(int)) );( cudaMemcpy(d_Input, h_Input, VECTOR_SIZE * sizeof(int),
cudaMemcpyHostToDevice) );::cout << "Running GPU reduction...\n";();(d_Output,
d_Input);();
std::cout << "Reading
back GPU results...\n";( cudaMemcpy(h_Output, d_Output, BLOCKS *
sizeof(int), cudaMemcpyDeviceToHost) );::cout << "Results:
\n";::copy(h_Output, h_Output + BLOCKS,
std::ostream_iterator<int>(std::cout,", "));( cudaFree(d_Input)
);( cudaFree(d_Output) );[] h_Input;[] h_Output;();
cutilExit(argc, argv);}
Технический вывод программыof
data: 8192: 16and initializing CUDA arrays...GPU reduction...back GPU
results...:
, 512, 512, 512, 512, 512, 512, 512,
512, 512, 512, 512, 512, 512, 512, 512ENTER to exit...
Код
программы суммирование вектора CPU
#include
<iostream>
#include <ctime>
#include <vector>
#include <algorithm>
#include <iterator>show (const
std::vector <float> &);main (void)
{int SIZE=5;::vector <float>
A;::vector <float> B;::vector <float> REZULT (SIZE);tmp;
// fill vector A(int i=0; i<SIZE;
i++) {::cout << "A[" << i <<"]: ";::cin
>> tmp;.push_back (tmp); }
// fill vector B(int i=0; i<SIZE;
i++) {::cout << "B[" << i <<"]: ";::cin
>> tmp;.push_back (tmp); }
// ::vector <float> ::
iterator A_=A.begin();::vector <float> :: iterator B_=B.begin();::vector
<float> :: iterator REZULT_=REZULT.begin();(REZULT_;
REZULT_!=REZULT.end(); REZULT_++, A_++, B_++)
*(REZULT_)=*(A_) + *(B_);= clock() -
time;
// output::cout <<
"Vector A: ";(A);::cout << "Vector B: ";(B);::cout
<< "Vector REZULT: ";(REZULT);
system ("pause");0;
}
void show (const std::vector
<float> &vec)
{::copy (vec.begin(), vec.end(),
std::ostream_iterator <int> (std::cout, " "));
std::cout
<< std::endl;
}
Код
программы расчёта БПФ с использованием библиотеки jCuda
public class JCufftDemo {static void
main(String[] args) {[] fftResults; dataSize = 1<<23;
System.out.println("Генерация
входных данных размером "+dataSize+"
значений...\n");
float[] inputData =
createRandomData(dataSize)
System.out.println("1D БПФ с использованием
apache commons math...");=
commonsTransform(floatDataToDoubleData(inputData.clone()));(fftResults);
System.out.println();
System.out.println("1D
БПФ JCufft (данные в оперативной памяти)...");
fftResults =
jcudaTransformHostMemory(inputData.clone());(fftResults);
System.out.println();.out.println("1D
БПФ
JCufft (данные в памяти видеокарты)...");=
jcudaTransformDeviceMemory(inputData.clone());(fftResults);
}
/**
*
Генерирует массив случайных чисел
*
*
@param dataSize - размер генерируемого массива
* @return массив случайных чисел
*/static float[]
createRandomData(int dataSize){random = new Random();data[] = new
float[dataSize];(int i = 0; i < dataSize; i++)[i] = random.nextFloat();
return data;
}
/**
*
Конвертирует массив значений типа float в массив значений double
*
*
@param data - массив который нужно конвертировать
* @return - сконвертированный массив
*/static double[]
floatDataToDoubleData(float[] data){ [] doubleData = new double[data.length];
(int i=0; i < data.length; i++) doubleData[i] = data[i];
return
doubleData;
}
/**
*
Выполняет БПФ массива значений с помощью CUDA, осуществляя
*
операции с данными в оперативной памяти
*
*
@param inputData - массив входных значений
*
@return массив с результатами БПФ
*/static double[]
jcudaTransformHostMemory(float[] inputData){[] fftResults = new
float[inputData.length + 2];
// создание планаplan
= new cufftHandle();.cufftPlan1d(plan, inputData.length, cufftType.CUFFT_R2C,
1);
// выполнение БПФtimeStart
= new Date().getTime();.cufftExecR2C(plan, inputData,
fftResults);.out.println("Время преобразования:
" + (new Date().getTime() - timeStart)/1000.0+" сек");
// уничтожение плана.cufftDestroy(plan);
return
cudaComplexToDouble(fftResults);
}
/**
* Выполняет БПФ массива значений с помощью CUDA,
осуществляя
*
операции с данными в памяти видеокарты
*
*
@param inputData - массив входных значений
*
@return массив с результатами БПФ
*/static double[]
jcudaTransformDeviceMemory(float[] inputData){[] fftResults = new
float[inputData.length + 2];
//
указатель на устройствоdeviceDataIn = new Pointer();
//
выделение памяти на видеокарте для входных данных
JCuda.cudaMalloc(deviceDataIn, inputData.length
* 4);
//
копирование входных данных в память видеокарты
JCuda.cudaMemcpy(deviceDataIn,
Pointer.to(inputData), inputData.length * 4, .cudaMemcpyHostToDevice);
Pointer deviceDataOut = new
Pointer();
//
выделение памяти на видеокарте для результатов преобразования
JCuda.cudaMalloc(deviceDataOut,
fftResults.length * 4);
// создание планаplan
= new cufftHandle();.cufftPlan1d(plan, inputData.length, cufftType.CUFFT_R2C,
1);
/*
*plan
- указатель на план
*inputDataSize
- количество входных значений; если входные данные являются *вещественными, то
этот параметр равняется длине входного массива, а если *входные данные это
массив комплексных чисел, то значение параметра равно *половине длины входного
массива, т.к. одно комплексное число представлено *двумя элементами массива
*cufftType.CUFFT_R2C
- тип преобразования
*1
- количество подобных преобразований
*/
// выполнение БПФtimeStart
= new Date().getTime();.cufftExecR2C(plan, deviceDataIn,
deviceDataOut);.out.println("Время преобразования:
" + (new Date().getTime() - timeStart)/1000.+" сек")
//
копирование результатов из памяти видеокарты в оперативную память
JCuda.cudaMemcpy(Pointer.to(fftResults),
deviceDataOut, fftResults.length * 4, .cudaMemcpyDeviceToHost);
// освобождение
ресурсов.cufftDestroy(plan);.cudaFree(deviceDataIn);.cudaFree(deviceDataOut);cudaComplexToDouble(fftResults);
}
/**
* Выполняет
БПФ
массива
значений
с
помощью
Apache
Commons Math
*
* @param inputData - массив входных значений
* @return массив с результатами БПФ
*/static double[]
commonsTransform(double[] inputData){fft = new
FastFourierTransformer();timeStart = new Date().getTime();[] cmx =
fft.transform(inputData);.out.println("Время
преобразования:
" + (new Date().getTime() - timeStart)/1000.+" сек");[]
fftReults = new double[inputData.length/2 + 1];(int i = 0; i <
fftReults.length; i++){[i] = cmx[i].abs();
}fftReults;
}
/**
* Метод осуществляет преобразование массива
комплексных чисел в
* массив, содержащий их модули
*
* @param complexData - массив комплексных чисел
* @return массив модулей комплексных чисел
*/
public static double[]
cudaComplexToDouble(float[] complexData){[] result = new
double[complexData.length/2];j=0;(int i=0; i < complexData.length-1; i++) {
[j++] = Math.sqrt(complexData[i]*complexData[i] +
complexData[i+1]*complexData[i+1]);
i++;
}result;
}
/**
* Выводит на стандартный вывод первое, среднее
* и последнее значения массива
*
* @param data - массив, значения которого
необходимо вывести
*/static void
printSomeValues(double[] data){ .out.println("[0]:
"+data[0]);.out.println("["+(data.length/2)+"]:
"+data[data.length/2]);.out.println("["+(data.length-1)+"]:
"+data[data.length-1]);
}
}
Технический вывод программы
Генерация входных данных размером 8388608
значений...
D БПФ с использованием apache commons math...
Время преобразования: 92.851 сек
[0]: 4195107.645827353
[2097152]: 1411.8392665442454
[4194304]: 114.60465306043625
D БПФ JCufft (данные в оперативной памяти)...
Время преобразования: 0.085 сек
[0]: 4195107.922955976
[2097152]: 1411.791370918522
[4194304]: 114.75
D БПФ JCufft (данные в памяти видеокарты)...
Время преобразования: 0.0 сек
[0]: 4195107.922955976
[2097152]: 1411.791370918522
[4194304]: 114.75
Код программы расчёта числа PI
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <conio.h>
#include <time.h>
#include <cuda.h>
#include <math.h>
#include <Windows.h>
#define CUDA_FLOAT floatlong
GRID_SIZE=1024;long BLOCK_SIZE=16;
__global__ void pi_kern(CUDA_FLOAT
*res)
{n = threadIdx.x + blockIdx.x *
BLOCK_SIZE;_FLOAT x0 = n * 1.f / (BLOCK_SIZE * GRID_SIZE); // Началоотрезка
интегрирования_FLOAT
y0 = sqrtf(1 - x0 * x0);_FLOAT dx = 1.f / (1.f * BLOCK_SIZE * GRID_SIZE); // Шагинтегрирования
CUDA_FLOAT s = 0; // Значение интеграла по
отрезку, данномутекущему треду
CUDA_FLOAT x1, y1;= x0 + dx;=
sqrtf(1 - x1 * x1);= (y0 + y1) * dx / 2.f;// Площадь
трапеции
res[n] = s;// Запись результата в глобальную
память
}main(int argc, char** argv)
{_t time;_FLOAT *res_d;// Результаты на устройстве
CUDA_FLOAT res[GRID_SIZE *
BLOCK_SIZE];// Результаты
в
хостовой
памяти((void**)&res_d,
sizeof(CUDA_FLOAT) * GRID_SIZE *
BLOCK_SIZE);// Выделение памяти на CPUл
// Рамеры грида и блока на GPU
dim3
grid(GRID_SIZE);block(BLOCK_SIZE);= clock();_kern<<<grid,
block>>>(res_d);// Запуск
ядра();//
Ожидаем
завершения
работы
ядра(res,
res_d, sizeof(CUDA_FLOAT) * GRID_SIZE * BLOCK_SIZE,);// Копируем
результаты
на
хост(res_d);//
Освобождаем
память
на
GPU_FLOAT pi = 0;(int i=0; i < GRID_SIZE * BLOCK_SIZE; i++)
{+= res[i];
}*= 4;= clock() - time;("PI =
%.12f\n",pi);("%.4f", (double)time/CLOCKS_PER_SEC);
getch();0;
}
Технический вывод программы
Расчёты проводились 10 раз. 100 000 000
Слагаемых для решения числа PI, время работы программы 35~37 секунд
Расчёт на CPU
|
Потоки,
количество
|
Итерации
|
1
|
2
|
4
|
8
|
16
|
32
|
100000
|
6
|
3
|
2
|
3
|
3
|
6
|
1000000
|
39
|
23
|
13
|
14
|
14
|
10000000
|
268
|
143
|
87
|
87
|
88
|
89
|
100000000
|
2178
|
1120
|
666
|
598
|
680
|
683
|
1000000000
|
26665
|
13289
|
7395
|
6202
|
6258
|
6483
|
На CPU 100 000 000 итераций занимает 598 секунд.
Выводы
При расчете числа Пи на CPU по результатам
проделанной работы стало известно, что чем больше количество итераций, тем
эффективнее работает параллельный алгоритм.
Самое эффективное - количество потоков =
количеству ядер процессора.
На не больших количествах итераций система
hyper-t не дает преимущества, за счет того, что потоки слишком быстро
заканчивают свое действие. Но при более больших объемах вычислений, hyper-t
позволяет и дальше увеличивать производительность поточного программирования,
за счет удвоения числа реально работающих в параллельном режиме потоков,
примерно на 20%.
На более высоких значениях количества итераций
максимальное ускорение достигается на 8 потоках, из-за того, что данный
процессор поддерживает технологию HyperThreading.
В процессорах с использованием этой технологии
каждый физический процессор может хранить состояние сразу двух потоков, что для
операционной системы выглядит как наличие двух логических процессоров (англ.
Logical processor). Физически у каждого из логических процессоров есть свой
набор регистров и контроллер прерываний (APIC), а остальные элементы процессора
являются общими. Когда при исполнении потока одним из логических процессоров
возникает пауза (в результате кэш-промаха, ошибки предсказания ветвлений, ожидания
результата предыдущей инструкции), то управление передаётся потоку в другом
логическом процессоре. Таким образом, пока один процесс ждёт, например, данные
из памяти, вычислительные ресурсы физического процессора используются для
обработки другого процесса.
Параллельные вычисления в целом более
эффективнее, чем последовательные, из-за того, они используют всю
вычислительную мощь процессора.
Расчёты на GPU быстрее в 17 раз, чем на CPU.
Высокая вычислительная мощность GPU объясняется
особенностями архитектуры. Если современные CPU содержат несколько ядер (на
большинстве современных систем от 2 до 6, по состоянию на 2012 г.), графический
процессор изначально создавался как многоядерная структура, в которой
количество ядер может достигать сотен. Разница в архитектуре обусловливает и
разницу в принципах работы. Если архитектура CPU предполагает последовательную
обработку информации, то GPU исторически предназначался для обработки
компьютерной графики, поэтому рассчитан на массивно параллельные вычисления.
Приложение 1
Теоретическая часть
иррациональное число, то есть его значение не
может быть точно выражено в виде дроби m/n, где m и n - целые числа.
Следовательно, его десятичное представление никогда не заканчивается и не
является периодическим
В своей лабораторной работе я использовал два
алгоритма расчета числа Пи .
Формула Леонарда Эйлера:
И Формулу Валлеса:
Последовательный алгоритм.
Схема 1.
Код программы представлен в
приложении 1.
Параллельный алгоритм
Узким местом параллельного алгоритма
является сохранение промежуточных сумм и произведений в один класс данных.
Чтобы этого избежать я сделал специальный
поток в программе, который распределяет нагрузку между потоками, которые
считают промежуточные суммы и произведения. А затем, после окончания расчета,
отправляют данные в один глобальный класс, который защищен мьютексом, чтобы
только один поток, в одно время мог записывать данные в переменную.
После поступления всех данных в
общий глобальный класс, поток который распределял нагрузку между потоками,
начинает считать сумму или произведение, в зависимости от алгоритма.
Такое распределение нагрузки позволило
избежать постоянных задержек в ожидании записи данных.
См. схема 2, код представлен в
приложении 2.
Алгоритм
Алгоритм расчёта чиста PI.
Вычислительный процессор
Core i7-950
Тактовая
частота
|
Ядра
|
Кэш
L2
|
Кэш
L3
|
TDP
|
Разъём
|
3.07
ГГц
|
4
|
4
× 256 КБ
|
8
МБ
|
3
× DDR3-1066
|
LGA
1366
|
Результаты эксперимента
|
Потоки,
количество
|
Итерации
|
1
|
2
|
4
|
8
|
16
|
32
|
100000
|
6
|
3
|
2
|
3
|
3
|
6
|
1000000
|
39
|
23
|
13
|
14
|
14
|
17
|
10000000
|
268
|
143
|
87
|
87
|
88
|
89
|
100000000
|
2178
|
1120
|
666
|
598
|
680
|
683
|
1000000000
|
26665
|
13289
|
7395
|
6202
|
6258
|
6483
|
Таблица 1. Таблица зависимости времени
выполнения, от количества итераций и потоков
Минимальное время в миллисекундах выделено серым
цветом.
|
Потоки
|
Итерации
|
1
|
2
|
4
|
8
|
16
|
32
|
100000
|
1
|
2
|
3
|
2
|
2
|
1
|
1000000
|
1
|
1,70
|
3,00
|
2,79
|
2,79
|
2,29
|
10000000
|
1
|
1,87
|
3,08
|
3,08
|
3,05
|
3,01
|
100000000
|
1
|
1,94
|
3,27
|
3,64
|
3,20
|
3,19
|
1000000000
|
1
|
2,01
|
3,61
|
4,30
|
4,26
|
4,11
|
|
|
|
|
|
|
|
Таблица 2. Таблица зависимости ускорения, от
количества итераций и потоков
Максимальное ускорение выделено серым цветом.
Рисунок 0. График зависимости ускорения, от
количества итераций и потоков.
ПРИЛОЖЕНИЕ 1.1
Рис.1 Хронология загрузки ЦП 1 000 000 000
итераций, 1 процесс
Рис.2 Хронология загрузки ЦП 1 000 000 000
итераций, 2 процесса
Рис.3 Хронология загрузки ЦП 1 000 000 000
итераций, 4 процесса
Рис.4 Хронология загрузки ЦП 1 000 000 000
итераций, 8 процессов
Рис.5 Хронология загрузки ЦП 1 000 000 000
итераций, 16 процессов
Рис.6 Хронология загрузки ЦП 1 000 000 000
итераций, 32 процесса
ПРИЛОЖЕНИЕ
1.2
private void
jButton1MouseClicked(java.awt.event.MouseEvent evt)
{//GEN-FIRST:event_jButton1MouseClickediter =
Integer.parseInt(jTextField1.getText());threads = Integer.parseInt(jTextField2.getText());.TimeStart();L
= new Listner(iter, threads, 0);t = new Thread(L);.start();{.join();
} catch (InterruptedException ex)
{.getLogger(multithread.class.getName()).log(Level.SEVERE, null, ex);
}.insert(Double.toString(Global.F())
+ "\n", 0);.TimeStop();.insert(Long.toString(Global.stop -
Global.start) + "\n", 0);.Reset();
}//GEN-LAST:event_jButton1MouseClickedclass
Listner extends Thread {threads, pos;iter;Listner(long iter, int threads, int
pos) {.threads = threads;.iter = iter;.pos = pos;
}
@Overridevoid run() {start = 1;delta
= iter / threads;[] t = new Thread[threads];(pos == 0) {[] f = new
First[threads];(int i = 0; i < threads; i++) {[i] = new First(start, start +
delta);[i] = new Thread(f[i], "Thread " + Integer.toString(i));[i].start();=
start + delta;
}
}(pos == 1) {[] f = new
Second[threads];(int i = 0; i < threads; i++) {[i] = new Second(start, start
+ delta);[i] = new Thread(f[i], "Thread " +
Integer.toString(i));[i].start();= start + delta;
}
}
for (int i = 0; i < threads; i++)
{{[i].join();
} catch (InterruptedException ex)
{.getLogger(Listner.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}class First extends Thread {start,
stop;First(long start, long stop) {.start =start;.stop = stop;
}
@Overridevoid run() {summ = 0;(int i
= (int)start; i < stop; i++) {+= 1 / Math.pow(i, 2);
}.plus(summ);
}
}
public class Global
private static double SumF = 0, SumS
= 1;static long start, stop;static synchronized void plus(double d) {+= d;
}static synchronized void mul(double
d) {*= d;
}static synchronized double F()
{Math.sqrt(6 * SumF);
}static synchronized double S()
{SumS * 2;
}static synchronized void
TimeStart() {= System.currentTimeMillis();
}static synchronized void TimeStop()
{
stop = System.currentTimeMillis();
}
Скриншоты программ
Инструкция пользователя
Программы имеют графический интерфейс, и
различаются иконками.
Программа использующая последовательный алгоритм
расчета пи имеет иконку: (onethread.exe), а параллельный : (multithread.exe)
Программа onethread.exe
Программа имеет 3 кнопки
«Первый» - нажимая эту кнопку вы запускаете
первый алгоритм(алгоритм Эйлера)
«Второй» - нажимая эту кнопку вы запускаете
второй алгоритм (алгоритм Валлеса)
««clear()» - очистка полей
В поле «количество итераций» вводится количество
итераций от 1 до 100 000 000 000
В поле «количество прогонов» вводится количество
повторений алгоритма, для более точного значения.
В левом поле выводится число Пи, в правом время
работы алгоритма.
Программа multithread.exe
Программа имеет 2 кнопки
«Первый» - нажимая эту кнопку вы запускаете
первый алгоритм(алгоритм Эйлера)
«Второй» - нажимая эту кнопку вы запускаете
второй алгоритм (алгоритм Валлеса)
В поле «количество итераций» вводится количество
итераций от 1 до 100 000 000 000
В поле «количество потоков» вводится количество
повторений алгоритма, для более точного значения.
В левом поле выводится число Пи, в правом время
работы алгоритма.
Выводы по лабораторной работе
При расчете числа Пи на CPU по результатам
проделанной работы стало известно, что чем больше количество итераций, тем
эффективнее работает параллельный алгоритм.
Самое эффективное - количество потоков =
количеству ядер процессора.
На не больших количествах итераций система
hyper-t не дает преимущества, за счет того, что потоки слишком быстро
заканчивают свое действие. Но при более больших объемах вычислений, hyper-t
позволяет и дальше увеличивать производительность поточного программирования,
за счет удвоения числа реально работающих в параллельном режиме потоков,
примерно на 20%.
На более высоких значениях количества итераций
максимальное ускорение достигается на 8 потоках, из-за того, что данный
процессор поддерживает технологию HyperThreading.
В процессорах с использованием этой технологии
каждый физический процессор может хранить состояние сразу двух потоков, что для
операционной системы выглядит как наличие двух логических процессоров (англ.
Logical processor). Физически у каждого из логических процессоров есть свой
набор регистров и контроллер прерываний (APIC), а остальные элементы процессора
являются общими. Когда при исполнении потока одним из логических процессоров
возникает пауза (в результате кэш-промаха, ошибки предсказания ветвлений,
ожидания результата предыдущей инструкции), то управление передаётся потоку в
другом логическом процессоре. Таким образом, пока один процесс ждёт, например,
данные из памяти, вычислительные ресурсы физического процессора используются для
обработки другого процесса.
Параллельные вычисления в целом более
эффективнее, чем последовательные, из-за того, они используют всю
вычислительную мощь процессора.
1.