1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
|
// Arithmetic tasks
// (c) EB Nov 2009
#include <stdlib.h>
#include "ArithmeticTasks.h"
#ifdef Linux
typedef short __int16;
typedef int __int32;
typedef long long int __int64;
#endif
// Get the ratio of user bits per stored bits for a given word size
// (constants defined in OpenCL code)
inline double useRatio(int ws)
{
switch (ws)
{
case 16: return 14.0/16.0;
case 32: return 30.0/32.0;
case 64: return 62.0/64.0;
}
return 1.0;
}
double AddNGPUTask::run(int workgroupSize,size_t sz)
{
if (!mBuildOK) return -1;
cl::Context * c = getContext();
cl::CommandQueue * q = getQueue();
cl::Program * p = getProgram();
if (c == 0 || q == 0 || p == 0) return -1;
// Check allocation size
size_t max_sz = c->getDeviceMaxMemAllocSize(); // Max global mem size
if (3*sz > max_sz) return -1;
cl::Buffer * a = c->createBuffer(CL_MEM_READ_WRITE,sz);
cl::Buffer * b = c->createBuffer(CL_MEM_READ_WRITE,sz);
cl::Buffer * y = c->createBuffer(CL_MEM_READ_WRITE,sz);
cl::Kernel * kernel = 0;
if (mVariant == ADDN_V1) kernel = p->createKernel("add_v1");
else if (mVariant == ADDN_V2) kernel = p->createKernel("add_v2");
unsigned char * buf = (unsigned char *)_aligned_malloc(sz,16);
int n = (int)sz / (mWS>>3); // N words in SZ
double mbps = -1;
if (kernel == 0 || a == 0 || b == 0 || y == 0 || buf == 0) goto END;
// Initialize A and B
for (size_t i=0;i<sz;i++) buf[i] = (unsigned char)(rand() & 0xFF);
if (q->writeBuffer(a,true,0,sz,buf) == CL_FALSE) goto END;
for (size_t i=0;i<sz;i++) buf[i] = (unsigned char)(rand() & 0xFF);
if (q->writeBuffer(b,true,0,sz,buf) == CL_FALSE) goto END;
// Run tests, double nOps until min time is reached
kernel->setArg(0,a);
kernel->setArg(1,b);
kernel->setArg(2,y);
if (q->execKernel1(kernel,n,workgroupSize) == CL_FALSE) goto END;
for (int nOps = 1; ; nOps <<= 1)
{
double t0 = getT();
for (int i=0;i<nOps;i++) q->execKernel1(kernel,n,workgroupSize);
q->finish();
double t = (getT() - t0);
if (t < MIN_RUNNING_TIME) continue;
// OK, t is large enough
t /= (double)nOps;
mbps = (double)sz*useRatio(mWS)*1.0e-6/t; // MB/s
break;
}
END: // Cleanup
if (buf != 0) _aligned_free(buf);
if (a != 0) delete a;
if (b != 0) delete b;
if (y != 0) delete y;
if (kernel != 0) delete kernel;
return mbps;
}
double Mul1GPUTask::run(int workgroupSize,size_t sz)
{
if (!mBuildOK) return -1;
cl::Context * c = getContext();
cl::CommandQueue * q = getQueue();
cl::Program * p = getProgram();
if (c == 0 || q == 0 || p == 0) return -1;
// Check allocation size
size_t max_sz = c->getDeviceMaxMemAllocSize(); // Max global mem size
if (2*sz > max_sz) return -1;
cl::Buffer * a = c->createBuffer(CL_MEM_READ_WRITE,sz);
cl::Buffer * y = c->createBuffer(CL_MEM_READ_WRITE,sz);
cl::Kernel * kernel = 0;
if (mVariant == MUL1_V1) kernel = p->createKernel("mul1_v1");
unsigned char * buf = (unsigned char *)_aligned_malloc(sz,16);
int n = (int)sz>>2; // 4 bytes/word
const int kk = 0x2FEFEFEF;
double mbps = -1;
if (kernel == 0 || a == 0 || y == 0 || buf == 0) goto END;
// Initialize A
for (size_t i=0;i<sz;i++) buf[i] = (unsigned char)(rand() & 0xFF);
if (q->writeBuffer(a,true,0,sz,buf) == CL_FALSE) goto END;
// Run tests, double nOps until min time is reached
kernel->setArg(0,kk);
kernel->setArg(1,a);
kernel->setArg(2,y);
if (q->execKernel1(kernel,n,workgroupSize)== CL_FALSE) goto END;
for (int nOps = 1; ; nOps <<= 1)
{
double t0 = getT();
for (int i=0;i<nOps;i++) q->execKernel1(kernel,n,workgroupSize);
q->finish();
double t = (getT() - t0);
if (t < MIN_RUNNING_TIME) continue;
// OK, t is large enough
t /= (double)nOps;
mbps = (double)sz*useRatio(32)*1.0e-6/t; // MB/s
break;
}
END: // Cleanup
if (buf != 0) _aligned_free(buf);
if (a != 0) delete a;
if (y != 0) delete y;
if (kernel != 0) delete kernel;
return mbps;
}
// CPU
struct AddNThreadParam
{
int nOps; // number of loops
int index; // Block index
size_t sz; // size to process in thread
int wordSize; // 32 or 64
int variant; // ADD_V1, ADD_V2
const unsigned char * a;
const unsigned char * b;
unsigned char * out;
};
template <typename T,int LOG_BASE> void addNv1(AddNThreadParam * p)
{
size_t n = p->sz / sizeof(T); // Words in block
const T * a = (const T *)(p->a);
const T * b = (const T *)(p->b);
T * out = (T *)(p->out);
T BASE = (T)1<<(T)LOG_BASE;
T BASE_MINUS1 = BASE - (T)1;
for (int it=0;it<p->nOps;it++)
{
T t = 0; // "carry" from previous word
if (p->index > 0)
{
// Get "carry" for last values of previous block
t = (a[-1] + b[-1]) >> (T)LOG_BASE;
}
for (size_t i=0;i<n;i++)
{
T s = a[i] + b[i];
out[i] = (s & BASE_MINUS1) + t;
t = s >> (T)LOG_BASE;
}
}
}
template <typename T,int LOG_BASE> void addNv2(AddNThreadParam * p)
{
size_t n = p->sz / sizeof(T); // Words in block
const T * a = (const T *)(p->a);
const T * b = (const T *)(p->b);
T * out = (T *)(p->out);
for (int it=0;it<p->nOps;it++)
{
for (size_t i=0;i<n;i++)
{
out[i] = a[i] + b[i];
}
}
}
#ifndef Linux // Uses Win32 intrinsics
void addNv3(AddNThreadParam * p)
{
const int LOG_BASE = 30;
const int BASE = 1<<LOG_BASE;
const int BASE_MINUS1 = BASE - 1;
size_t n = p->sz >> 4; // We process blocks of 16 bytes
const __m128i * a = (const __m128i *)(p->a);
const __m128i * b = (const __m128i *)(p->b);
__m128i * out = (__m128i *)(p->out);
__m128i t;
__m128i mask = _mm_set1_epi32(BASE_MINUS1);
for (int it=0;it<p->nOps;it++)
{
// TODO: initialize T with the carry from the end of previous block
t = _mm_setzero_si128();
for (size_t i=0;i<n;i++)
{
__m128i s = _mm_add_epi32(_mm_load_si128(a+i),_mm_load_si128(b+i));
__m128i o = _mm_add_epi32(_mm_and_si128(s,mask),t);
_mm_store_si128(out+i,o);
t = _mm_srli_epi32(s,LOG_BASE);
}
}
}
#endif
thread_return_t AddNThread(void * x)
{
AddNThreadParam * p = (AddNThreadParam *)x;
if (p->wordSize == 16 && p->variant == ADDN_V1) addNv1<__int16,14>(p);
else if (p->wordSize == 32 && p->variant == ADDN_V1) addNv1<__int32,30>(p);
else if (p->wordSize == 64 && p->variant == ADDN_V1) addNv1<__int64,62>(p);
else if (p->wordSize == 16 && p->variant == ADDN_V2) addNv2<__int16,14>(p);
else if (p->wordSize == 32 && p->variant == ADDN_V2) addNv2<__int32,30>(p);
else if (p->wordSize == 64 && p->variant == ADDN_V2) addNv2<__int64,62>(p);
#ifndef Linux
else if (p->wordSize == 32 && p->variant == ADDN_V3) addNv3(p);
#endif
return 0;
}
double AddNCPUTask::run(int nThreads,size_t sz)
{
if (nThreads <= 0 || sz <= 0) return -1; // invalid
size_t sz1 = sz / nThreads;
if (sz1 <= 0) return -1; // SZ too small
unsigned char * in_a = (unsigned char *)_aligned_malloc(sz,ALLOC_ALIGN);
unsigned char * in_b = (unsigned char *)_aligned_malloc(sz,ALLOC_ALIGN);
unsigned char * out = (unsigned char *)_aligned_malloc(sz,ALLOC_ALIGN);
double mbps = -1;
std::vector<AddNThreadParam> params(nThreads);
for (int nOps=1; ;nOps<<=1)
{
// Initialize memory
for (size_t i=0;i<sz;i++)
{
in_a[i] = (unsigned char)(i & 0xFF);
in_b[i] = (unsigned char)((i+99) & 0xFF);
out[i] = 0;
}
// setup params
for (int i=0;i<nThreads;i++)
{
params[i].a = in_a + i*sz1;
params[i].b = in_b + i*sz1;
params[i].out = out + i*sz1;
params[i].sz = sz1;
params[i].nOps = nOps;
params[i].wordSize = mWS;
params[i].variant = mVariant;
params[i].index = i;
}
// run threads
double t = runCPUThreads(params,AddNThread);
// Check result
if (nOps == 1)
{
// ZZZ: do it!
}
if (t < MIN_RUNNING_TIME) continue; // Too short
t /= (double)nOps;
mbps = (double)sz1*(double)nThreads*useRatio(mWS)*1.0e-6/t;
break;
} // nOps loop
_aligned_free(in_a);
_aligned_free(in_b);
_aligned_free(out);
return mbps;
}
|