Этот пример показывает сравнение четырех методов, которые могут использоваться для ускорения симуляций вероятности битовой ошибки (BER) с использованием системных объектов в программном обеспечении MATLAB ® Communications Toolbox™. Небольшая система, основанная на сверточном кодировании, иллюстрирует эффект генерации кода с помощью Coder™ продукта MATLAB ®, выполнения параллельного цикла с использованием parfor
в продукте Parallel Computing Toolbox™ комбинация генерации кода и parfor
, и системные объекты на базе графического процессора.
Системные объекты этого примера доступны в продукте Communications Toolbox. Для порядка запуска этого примера необходимо иметь лицензию MATLAB Coder, лицензию Parallel Computing Toolbox и достаточный графический процессор.
Этот пример использует простую систему сверточного кодирования, чтобы проиллюстрировать стратегии ускорения симуляции. Система генерирует биты случайных сообщений, используя randi
. Передатчик кодирует эти биты, используя сверточный энкодер скорости 1/2, применяет схему модуляции QPSK и затем передает символы. Символы проходят через канал AWGN, где происходит повреждение сигнала. Демодуляция QPSK происходит в приемнике, и поврежденные биты декодируются с помощью алгоритма Viterbi. Наконец, вычисляется вероятность битовой ошибки. В этой системе используются следующие системные объекты:
comm.ConvolutionalEncoder - сверточное кодирование
comm.PSKModulator - QPSK модуляция
comm.AWGNChannel - канал AWGN
comm.PSKDemodulator - демодуляция QPSK (приблизительно LLR)
comm.ViterbiDecoder - декодирование Viterbi
Код для приемопередатчиков можно найти в:
Каждая точка вдоль кривой частоты битовой ошибки представляет результат многих итераций кода приемопередатчика, описанного выше. Чтобы получить точные результаты за разумное время, симуляция будет собирать по меньшей мере 200 битовых ошибок на значение отношения сигнал/шум (ОСШ) и самое большее 5000 пакетов данных. Пакет представляет 2000 биты сообщений. ОСШ находится в областях значений от 1 дБ до 5 дБ.
iterCntThreshold = 5000; minErrThreshold = 200; msgL = 2000; snrdb = 1:5;
Один раз вызовите функции приемопередатчика, чтобы увеличить время настройки и накладные расходы на конструкцию объекта. Объекты хранятся в постоянных переменных в каждой функции.
errs = zeros(length(snrdb),1); iters = zeros(length(snrdb),1); berplot = cell(1,5); numframes = 500; %GPU version runs 500 frames in parallel. viterbiTransceiverCPU(-10,1,1); viterbiTransceiverGPU(-10,1,1,numframes); N=1; %N tracks which simulation variant is run
Рабочий процесс для этого примера:
Запустите симуляцию базовой линии системных объектов
Используйте MATLAB Coder, чтобы сгенерировать MEX-функцию для симуляции
Используйте parfor, чтобы запустить параллельную симуляцию частоты битовой ошибки
Объедините сгенерированную MEX-функцию с parfor
Использование системных объектов на базе графического процессора
fprintf(1,'Bit Error Rate Acceleration Analysis Example\n\n');
Чтобы установить точку ссылки для различных стратегий ускорения, кривая частоты битовой ошибки генерируется только с использованием системных объектов. Код для приемопередатчика в viterbiTransceiverCPU.m
.
fprintf(1,'***Baseline - Standard System object simulation***\n'); % create random stream for each snrdb simulation s = RandStream.create('mrg32k3a','NumStreams',1,... 'CellOutput',true,'NormalTransform', 'Inversion'); RandStream.setGlobalStream(s{1}); ts = tic; for ii=1:numel(snrdb) fprintf(1,'Iteration number %d, SNR (dB) = %d\n',ii, snrdb(ii)); [errs(ii),iters(ii)] =viterbiTransceiverCPU(snrdb(ii), minErrThreshold, iterCntThreshold); end ber = errs./ (msgL* iters); baseTime=toc(ts); berplot{N} = ber; desc{N} = 'baseline'; reportResultsCommSysGPU(N, baseTime,baseTime, 'Baseline');
Используя MATLAB Coder, можно сгенерировать файл MEX с оптимизированным кодом С, который соответствует предварительно скомпилированному коду MATLAB. Потому что viterbiTransceiverCPU
функция соответствует подмножеству генерации кода MATLAB, его можно скомпилировать в MEX-функцию без изменения.
Для запуска этого фрагмента примера необходимо иметь лицензию MATLAB Coder.
fprintf(1,'\n***Baseline + codegen***\n'); N=N+1; %Increase simulation counter % Create the coder object and turn off checks which will cause low % performance. fprintf(1,'Generating Code ...'); config_obj = coder.config('MEX'); config_obj.EnableDebugging = false; config_obj.IntegrityChecks = false; config_obj.ResponsivenessChecks = false; config_obj.EchoExpressions = false; % Generate a MEX file codegen('viterbiTransceiverCPU.m', '-config', 'config_obj', '-args', {snrdb(1), minErrThreshold, iterCntThreshold} ) fprintf(1,' Done.\n'); %Run once to eliminate startup overhead. viterbiTransceiverCPU_mex(-10,1,1); s = RandStream.getGlobalStream; reset(s); % Use the generated MEX function viterbiTransceiverCPU_mex in the % simulation loop. ts = tic; for ii=1:numel(snrdb) fprintf(1,'Iteration number %d, SNR (dB) = %d\n',ii, snrdb(ii)); [errs(ii),iters(ii)] = viterbiTransceiverCPU_mex(snrdb(ii), minErrThreshold, iterCntThreshold); end ber = errs./ (msgL* iters); trialtime=toc(ts); berplot{N} = ber; desc{N} = 'codegen'; reportResultsCommSysGPU(N, trialtime,baseTime, 'Baseline + codegen');
Использование parfor
MATLAB выполняет код приемопередатчика против всех значений ОСШ параллельно. Это требует открытия параллельного пула и добавления parfor
цикл.
Для запуска этого фрагмента примера необходимо иметь лицензию Parallel Computing Toolbox.
fprintf(1,'\n***Baseline + parfor***\n'); fprintf(1,'Accessing multiple CPU cores ...\n'); if isempty(gcp('nocreate')) pool = parpool; poolWasOpen = false; else pool = gcp; poolWasOpen = true; end nW=pool.NumWorkers; N=N+1; %Increase simulation counter snrN = numel(snrdb); mT = minErrThreshold / nW; iT = iterCntThreshold / nW; errN = zeros(nW, snrN); itrN = zeros(nW, snrN); % replicate snrdb snrdb_rep=repmat(snrdb,nW,1); % create an independent stream for each worker s = RandStream.create('mrg32k3a','NumStreams',nW,... 'CellOutput',true,'NormalTransform', 'Inversion'); % pre-run parfor jj=1:nW RandStream.setGlobalStream(s{jj}); viterbiTransceiverCPU(-10, 1, 1); end fprintf(1,'Start parfor job ... '); ts = tic; parfor jj=1:nW for ii=1:snrN [err, itr] = viterbiTransceiverCPU(snrdb_rep(jj,ii), mT, iT); errN(jj,ii) = err; itrN(jj,ii) = itr; end end ber = sum(errN)./ (msgL*sum(itrN)); trialtime=toc(ts); fprintf(1,'Done.\n'); berplot{N} = ber; desc{N} = 'parfor'; reportResultsCommSysGPU(N, trialtime,baseTime, 'Baseline + parfor');
Можно объединить последние два метода для дополнительного ускорения. Скомпилированная MEX-функция может выполняться внутри parfor
цикл.
Для запуска этого фрагмента примера необходимо иметь лицензию MATLAB Coder и лицензию Parallel Computing Toolbox.
fprintf(1,'\n***Baseline + codegen + parfor***\n'); N=N+1; %Increase simulation counter % pre-run parfor jj=1:nW RandStream.setGlobalStream(s{jj}); viterbiTransceiverCPU_mex(1, 1, 1); % use the same mex file end fprintf(1,'Start parfor job ... '); ts = tic; parfor jj=1:nW for ii=1:snrN [err, itr] = viterbiTransceiverCPU_mex(snrdb_rep(jj,ii), mT, iT); errN(jj,ii) = err; itrN(jj,ii) = itr; end end ber = sum(errN)./ (msgL*sum(itrN)); trialtime=toc(ts); fprintf(1,'Done.\n'); berplot{N} = ber; desc{N} = 'codegen + parfor'; reportResultsCommSysGPU(N, trialtime,baseTime, 'Baseline + codegen + parfor');
Системные объекты, которые viterbiTransceiverCPU
использование функций доступно для выполнения на графическом процессоре. Версии на базе GPU:
comm.gpu.ConvolutionalEncoder - сверточное кодирование
comm.pu.PSKModulator - QPSK модуляция
comm.gpu.AWGNChannel - канал AWGN
comm.pu.PSKDemodulator - демодуляция QPSK (приблизительно LLR)
comm.pu.ViterbiDecoder - декодирование Viterbi
Графический процессор наиболее эффективен при обработке больших количеств данных сразу. Системные объекты на базе GPU могут обрабатывать несколько системы координат за один вызов метода шага. The numframes
переменная представляет количество систем координат, обрабатываемых на каждый вызов. Это аналогично parfor
за исключением того, что параллелизм определяется на основе каждого объекта, а не на viterbiTransceiverCPU
базис вызовов.
Для запуска этого фрагмента примера необходимо иметь лицензию Parallel Computing Toolbox и графический процессор с поддержкой CUDA ® 1.3.
fprintf(1,'\n***GPU***\n'); N=N+1; %Increase simulation counter try dev = parallel.gpu.GPUDevice.current; fprintf(... 'GPU detected (%s, %d multiprocessors, Compute Capability %s)\n',... dev.Name, dev.MultiprocessorCount, dev.ComputeCapability); sg = parallel.gpu.RandStream.create('mrg32k3a','NumStreams',1,'NormalTransform','Inversion'); parallel.gpu.RandStream.setGlobalStream(sg); ts = tic; for ii=1:numel(snrdb) fprintf(1,'Iteration number %d, SNR (dB) = %d\n',ii, snrdb(ii)); [errs(ii),iters(ii)] =viterbiTransceiverGPU(snrdb(ii), minErrThreshold, iterCntThreshold, numframes); end ber = errs./ (msgL* iters); trialtime=toc(ts); berplot{N} = ber; desc{N} = 'GPU'; reportResultsCommSysGPU(N, trialtime,baseTime, 'Baseline + GPU'); fprintf(1,' Done.\n'); catch %#ok<CTCH> % Report that the appropriate GPU was not found. fprintf(1, ['Could not find an appropriate GPU or could not ', ... 'execute GPU code.\n']); end
Сравнивая результаты этих испытаний, ясно, что графический процессор значительно быстрее, чем любой другой метод ускорения симуляции. Это повышение эффективности требует очень скромного изменения кода симуляции. Однако нет потерь в эффективности частоты битовой ошибки, как показано на следующем графике. Очень небольшие различия в кривых являются результатом различных алгоритмов генерации случайных чисел и/или эффектов усреднения различных величин данных для одной и той же точки на кривой.
lines = {'kx-.', 'ro-', 'cs--', 'm^:', 'g*-'}; for ii=1:numel(desc) semilogy(snrdb, berplot{ii}, lines{ii}); hold on; end hold off; title('Bit Error Rate for Various Acceleration Strategies'); xlabel('Signal to Noise Ratio (dB)'); ylabel('BER'); legend(desc{:});
Оставьте параллельный пул в исходном состоянии.
if ~poolWasOpen delete(gcp); end