LLVM IR与C++ MUL函数代码
LLVM IR与C++ MUL函数代码
使用LLVM IR写程序
熟悉LLVM IR最好的办法就是使用IR写几个程序。在开始写之前,建议先花30分钟-1个小时再粗略阅读下官方手册(https://llvm.org/docs/LangRef.html),熟悉下都有哪些指令的类型。接下来我们通过两个简单的case熟悉下LLVM IR编程的全部流程。
下面是一个循环加法的函数片段。这个函数一共包含三个Basic Block,loop、loop_body和final。其中loop是整个函数的开始,loop_body是函数的循环体,final是函数的结尾。在第5行和第6行,我们使用phi节点来实现结果和循环变量。
define i32 @ir_loopadd_phi(i32*, i32){ br label %loop loop: %i = phi i32 [0,%2], [%newi,%loop_body] %res = phi i32[0,%2], [%new_res, %loop_body] %break_flag = icmp sge i32 %i, %1 br i1 %break_flag, label %final, label %loop_body loop_body: %addr = getelementptr inbounds i32, i32* %0, i32 %i %val = load i32, i32* %addr, align 4 %new_res = add i32 %res, %val %newi = add i32 %i, 1 br label %loopfinal: ret i32 %res;}
下面是一个数组冒泡排序的函数片段。这个函数包含两个循环体。LLVM IR实现循环本身就比较复杂,两个循环嵌套会更加复杂。如果能够用LLVM IR实现一个冒泡算法,基本上就理解了LLVM的整个逻辑了。
define void @ir_bubble(i32*, i32) { %r_flag_addr = alloca i32, align 4 %j = alloca i32, align 4 %r_flag_ini = add i32 %1, -1 store i32 %r_flag_ini, i32* %r_flag_addr, align 4 br label %out_loop_headout_loop_head: ;check break store i32 0, i32* %j, align 4 %tmp_r_flag = load i32, i32* %r_flag_addr, align 4 %out_break_flag = icmp sle i32 %tmp_r_flag, 0 br i1 %out_break_flag, label %final, label %in_loop_head in_loop_head: ;check break %tmpj_1 = load i32, i32* %j, align 4 %in_break_flag = icmp sge i32 %tmpj_1, %tmp_r_flag br i1 %in_break_flag, label %out_loop_tail, label %in_loop_body in_loop_body: ;read & swap %tmpj_left = load i32, i32* %j, align 4 %tmpj_right = add i32 %tmpj_left, 1 %left_addr = getelementptr inbounds i32, i32* %0, i32 %tmpj_left %right_addr = getelementptr inbounds i32, i32* %0, i32 %tmpj_right %left_val = load i32, i32* %left_addr, align 4 %right_val = load i32, i32* %right_addr, align 4 ;swap check %swap_flag = icmp sge i32 %left_val, %right_val %left_res = select i1 %swap_flag, i32 %right_val, i32 %left_val %right_res = select i1 %swap_flag, i32 %left_val, i32 %right_val store i32 %left_res, i32* %left_addr, align 4 store i32 %right_res, i32* %right_addr, align 4 br label %in_loop_end in_loop_end: ;update j %tmpj_2 = load i32, i32* %j, align 4 %newj = add i32 %tmpj_2, 1 store i32 %newj, i32* %j, align 4 br label %in_loop_headout_loop_tail: ;update r_flag %tmp_r_flag_1 = load i32, i32* %r_flag_addr, align 4 %new_r_flag = sub i32 %tmp_r_flag_1, 1 store i32 %new_r_flag, i32* %r_flag_addr, align 4 br label %out_loop_headfinal: ret void}
我们把如上的LLVM IR用clang编译器编译成object文件,然后和C语言写的程序链接到一起,即可正常调用。在上面提到的case中,我们只使用了i32、i64等基本数据类型,LLVM IR中支持struct等高级数据类型,可以实现更为复杂的功能。
2.3 使用LLVM API实现Codegen
编译器本质上就是调用各种各样的API,根据输入去生成对应的代码,LLVM Codegen也不例外。在LLVM内部,一个函数是一个class,一个Basic Block试一个class, 一条指令、一个变量都是一个class。用LLVM API实现codegen就是根据需求,用LLVM内部的数据结构去实现相应的IR。
Value *constant = Builder.getInt32(16); Value *Arg1 = fooFunc->arg_begin(); Value *val = createArith(Builder, Arg1, constant); Value *val2 = Builder.getInt32(100); Value *Compare = Builder.CreateICmpULT(val, val2, "cmptmp"); Value *Condition = Builder.CreateICmpNE(Compare, Builder.getInt1(0), "ifcond"); ValList VL; VL.push_back(Condition); VL.push_back(Arg1); BasicBlock *ThenBB = createBB(fooFunc, "then"); BasicBlock *ElseBB = createBB(fooFunc, "else"); BasicBlock *MergeBB = createBB(fooFunc, "ifcont"); BBList List; List.push_back(ThenBB); List.push_back(ElseBB); List.push_back(MergeBB); Value *v = createIfElse(Builder, List, VL);
如上是一个用LLVM API实现codegen的例子。其实这就是个用C++写IR的过程,如果知道如何写IR的话,只需要熟悉下这套API就可以了。这套API提供了一些基本的数据结构,比如指令、函数、基本块、llvm builder等,然后我们只需要调用相应的函数去生成这些对象即可。一般来说,首先我们先生成函数的原型,包括函数名字、参数列表、返回类型等。然后我们在根据函数的功能,确定都需要有哪些Basic Block以及Basic Block之间的跳转关系,然后生成相应的Basic。最后我们再按照一定的顺序去给每个Basic Block填充指令。逻辑是,这个流程和用LLVM IR写代码是相仿的。
3. Codegen技术分析
如果我们用上文所描述的方法,生成一些简单的函数,并且用C写出对应的版本进行性能对比,我们就会发现,LLVM IR的性能并不会比C快。一方面,计算机底层执行的是汇编,C语言本身和汇编是非常接近的,了解底层的程序员往往能够从C代码中推测出大概会生成什么样的汇编。另一方面,现代编译器往往做了很多优化,一些大大减轻了程序员的优化负担。因此,使用LLVM IR进行Codegen并不会获得比手写C更好的性能,而且使用LLVM Codegen有一些明显的缺点。想要真正用好LLVM,我们还需要熟悉LLVM的特点。
3.1 缺点分析
缺点1:开发难。实际开发中几乎不会有工程使用汇编作为主要开发语言,因为开发难度太大了,有兴趣的小伙伴可以试着写个快排感受一下。即使是数据库、操作系统这样的基础软件,往往也只是在少数的地方会用到汇编。使用LLVM IR开发会有类似的问题。比如上文展示的最复杂例子是冒泡算法。开发者用C写个冒泡只需要几分钟,但是用LLVM IR写个冒泡可能要一个小时。另外,LLVM IR很难处理复杂的数据结构,比如结构体、类。除了LLVM IR中的那些基本数据结构外,新增一个复杂的数据结构非常难。因此在实际的开发当中,采用Codegen会导致开发难度指数级上升。
缺点2:调试难。开发者通常通过单步跟踪的方式去调试代码,但是LLVM IR是不支持的。一旦代码出问题,只能是人肉一遍一遍看LLVM IR。如果懂汇编的话,可以通过单步跟踪生成的汇编进行调试,但是汇编语言和IR之间并不是简单的映射关系,因此只能一定程度上降低调试难度,并不完全解决调试的问题。
缺点3: 运行成本。生成LLVM IR往往很快,但是生成的IR需要调用LLVM 中的工具进行优化、以及编译成二进制文件,这个过程是需要时间的(请联想一下GCC编译的速度)。在数据库的开发过程中,我们的经验值是每个函数大约需要10ms-100ms的codegen成本。大部分的时间花在了优化IR和IR到汇编这两步。
3.2 适用场景
了解了LLVM Codegen的缺点,我们才能去分析其优点、选择合适场景。下面这部分是团队在开发过程中总结的适合使用LLVM Codegen的场景。
场景1:Java/python等语言。上文中提到过LLVM IR并不会比C快,但是会比Java/python等语言快啊。例如在Java中,有时候为了提升性能,会通过JNI调用一些C的函数提升性能。同理,Java也可以调用LLVM IR生成的函数提升性能。
场景2:硬件和语言不兼容。LLVM支持多种后端,比如X86、ARM和GPU。对于一些硬件与语言不兼容的场景,可以利用LLVM实现兼容。例如如果我们的系统是用Java语言开发、想要调用GPU,可以考虑用LLVM IR生成GPU代码,然后通过JNI的方法进行调用。这套方案不仅支持NVIDIA的GPU,也支持AMD的GPU,而且对应生成的IR也可以在CPU上执行。
场景3:逻辑简化。以数据库为例,数据库执行引擎在执行过程中需要做大量的数据类型、算法逻辑相关的判断。这主要是由于SQL中的数据类型和逻辑,很多是在数据库开发时无法确定的,只能在运行时决定。这一部分过程,也被称为“解释执行”。我们可以利用LLVM在运行时生成代码,由于这个时候数据类型和逻辑已经确定,我们可以在LLVM IR中删除那些不必要的判断操作,从而实现性能的提升。
4. LLVM在数据库中的应用
在数据库当中,团队是用LLVM来进行表达式的处理,接下来我们以PostgreSQL数据库和云原生数据仓库AnalyticDB PostgreSQL为对比,解释LLVM的应用方法。
PostgreSQL为了实现表达式的解释执行,采用了一套“拼函数”的方案。PostgreSQL中实现了大量C函数,比如加减法、大小比较等,不同类型的都有。SQL在生成执行计划阶段会根据表达式符号的类型和数据类型选择相应的函数、把指针存下来,等执行的时候再调用。因此对于 "a > 10 and b < 5"这样的过滤条件,假设a和b都是int32,PostgreSQL实际上调用了“Int8AndOp(Int32GT(a, 10), Int32LT(b, 5))”这样一个函数组合,就像搭积木一样。这样的方案有两个明显的性能问题。一方面这种方案会带来比较多次数的函数调用,函数调用本身是有成本的。另一方面,这种方案必须要实现一个统一的函数接口,函数内部和外部都需要做一些类型转换,这也是额外的性能开销。Odyssey使用LLVM 进行codegen,可以实现最小化的代码。因为在SQL下发以后,数据库是知道表达式的符号和输入数据的类型的,因此只需要根据需求选取相应的IR指令就可以了。因此只需要三条IR指令,就可以实现这个表达式,然后我们把表达式封装成一个函数,就可以在执行的时候调用了。这次操作,把多次函数调用简化成了一次函数调用,大大减少了指令的总数量。
// 样例SQLselect count(*) from table where a > 10 and b < 5;// PostgreSQL解释执行方案:多次函数调用result = Int8AndOp(Int32GT(a, 10), Int32LT(b, 5));// AnalyticDB PostgreSQL方案:使用LLVM codegen生成最小化底层代码%res1 = icmp ugt i32 %a, 10;%res2 = icmp ult i32 %b, 5; %res = and i8 %res1, %res2;
在数据库中,表达式主要出现在几个场景。一类是过滤条件,通常出现在where条件中。一类是输出列表,一般跟在select之后。有些算子,比如join、agg等,它的判断条件中也可能会出现一些比较复杂的表达式。因此表达式的处理是会出现在数据库执行引擎的各个模块的。在AnalyticDB PostgreSQL版中,开发团队抽象出了一个表达式处理框架,通过LLVM Codegen来处理这些表达式,从而提高了执行引擎的整体性能。
LLVM作为一个流行的开源编译框架,近年来被用于数据库、AI等系统的性能加速。由于编译器理论本身门槛较高,因此LLVM的学习有一定的难度。而且从工程上,还需要对LLVM的工程特点和性能特征有比较准确的理解,才能找到合适的加速场景。阿里云数据库团队的云原生数据仓库产品AnalyticDB PostgreSQL版基于LLVM实现了一套运行时的表达式处理框架,能够有效地提高系统在进行复杂数据分析时地性能。
本文整理汇总了C++中MUL函数的典型用法代码示例。
一共展示了MUL函数的15个代码示例。
示例1: gradMag
void gradMag( float *I, float *M, float *O, int h, int w, int d, bool full ) {
x, y, y1, c, h4, s;
*Gx, *Gy, *M2;
__m128 *_Gx, *_Gy, *_M2, _m;
*acost = acosTable(), acMult =
;
h4 = (h %
==
) ? h : h - (h %
) +
;
s = d * h4 *
(
);
M2 = (
*) alMalloc(s,
);
_M2 = (__m128*) M2;
Gx = (
*) alMalloc(s,
);
_Gx = (__m128*) Gx;
Gy = (
*) alMalloc(s,
);
_Gy = (__m128*) Gy;
( x =
; x < w; x++ ) {
(c =
; c < d; c++) {
grad1( I + x * h + c * w * h, Gx + c * h4, Gy + c * h4, h, w, x );
( y =
; y < h4 /
; y++ ) {
y1 = h4 /
* c + y;
_M2[y1] = ADD(
(_Gx[y1], _Gx[y1]),
(_Gy[y1], _Gy[y1]));
( c ==
) {
; }
_m = CMPGT( _M2[y1], _M2[y] );
_M2[y] = OR( AND(_m, _M2[y1]), ANDNOT(_m, _M2[y]) );
_Gx[y] = OR( AND(_m, _Gx[y1]), ANDNOT(_m, _Gx[y]) );
_Gy[y] = OR( AND(_m, _Gy[y1]), ANDNOT(_m, _Gy[y]) );
}
}
( y =
; y < h4 /
; y++ ) {
_m = MINsse( RCPSQRT(_M2[y]), SET(
f) );
_M2[y] = RCP(_m);
(O) { _Gx[y] =
(
(_Gx[y], _m), SET(acMult) ); }
(O) { _Gx[y] = XOR( _Gx[y], AND(_Gy[y], SET(
)) ); }
};
( M + x * h, M2, h *
(
) );
( O !=
)
( y =
; y < h; y++ ) { O[x * h + y] = acost[(
)Gx[y]]; }
( O !=
&& full ) {
y1 = ((~
(O + x * h) +
) &
) /
;
y =
;
( ; y < y1; y++ ) { O[y + x * h] += (Gy[y] <
) * PI; }
( ; y < h -
; y +=
) STRu( O[y + x * h],
ADD( LDu(O[y + x * h]), AND(CMPLT(LDu(Gy[y]), SET(
)), SET(PI)) ) );
( ; y < h; y++ ) { O[y + x * h] += (Gy[y] <
) * PI; }
}
}
alFree(Gx);
alFree(Gy);
alFree(M2);
}
示例2: main
a,b;
c,d;
(
);
(
,&a,&b);
(
,a,b,
(a,b));
(
);
(
,&c,&d);
(
,c,d,
(c,d));
;
}
示例3: MUL
p[
], q[
], r[
], carry0, carry1;
(a[
], x[
], p);
ADDEQU(p[
], c, carry0);
ADDEQU(p[
], carry0, carry1);
(a[
], x[
], q);
ADDEQU(p[
], q[
], carry0);
(a[
], x[
], r);
x[
] = LOW(carry0 + carry1 + CARRY(p[
], r[
]) + q[
] + r[
] +
a[
] * x[
] + a[
] * x[
] + a[
] * x[
]);
x[
] = LOW(p[
] + r[
]);
x[
] = LOW(p[
]);
}
示例4: oob
Value* oob(const STREAMOUT_COMPILE_STATE& state, Value* pSoCtx, uint32_t buffer)
{
Value* returnMask = C(
);
Value* pBuf = getSOBuffer(pSoCtx, buffer);
Value* enabled = TRUNC(LOAD(pBuf, {
, SWR_STREAMOUT_BUFFER_enable }), IRB()->getInt1Ty());
Value* bufferSize = LOAD(pBuf, {
, SWR_STREAMOUT_BUFFER_bufferSize });
Value* streamOffset = LOAD(pBuf, {
, SWR_STREAMOUT_BUFFER_streamOffset });
Value* pitch = LOAD(pBuf, {
, SWR_STREAMOUT_BUFFER_pitch });
returnMask = OR(returnMask, NOT(enabled));
Value* newOffset = ADD(streamOffset,
(pitch, C(state.numVertsPerPrim)));
returnMask = OR(returnMask, ICMP_SGT(newOffset, bufferSize));
returnMask;
}
示例5: MUL
gen8_vec4_generator::generate_gs_set_write_offset(struct brw_reg dst,
struct brw_reg src0,
struct brw_reg src1)
{
/* From p22 of volume 4 part 2 of the Ivy Bridge PRM (2.4.3.1 Message
* Header: M0.3):
*
* Slot 0 Offset. This field, after adding to the Global Offset field
* in the message descriptor, specifies the offset (in 256-bit units)
* from the start of the URB entry, as referenced by URB Handle 0, at
* which the data will be accessed.
*
* Similar text describes DWORD M0.4, which is slot 1 offset.
*
* Therefore, we want to multiply DWORDs 0 and 4 of src0 (the x components
* of the register for geometry shader invocations 0 and 1) by the
* immediate value in src1, and store the result in DWORDs 3 and 4 of dst.
*
* We can do this with the following EU instruction:
*
* mul(2) dst.3<1>UD src0<8;2,4>UD src1 { Align1 WE_all }
*/
default_state.access_mode = BRW_ALIGN_1;
gen8_instruction *inst =
(suboffset(stride(dst,
,
,
),
), stride(src0,
,
,
), src1);
gen8_set_mask_control(inst, BRW_MASK_DISABLE);
default_state.access_mode = BRW_ALIGN_16;
}
示例6: LEA
LEA(
, tempReg1, MScaled(uReg, SCALE_4,
));
AND(
, R(tempReg1), Imm8(~
));
LEA(
, tempReg2, MScaled(vReg, SCALE_4,
));
AND(
, R(tempReg2), Imm8(
));
LEA(
, tempReg1, MComplex(tempReg1, tempReg2, SCALE_4,
));
ADD(
, R(tempReg1), R(srcReg));
SHR(
, R(vReg), Imm8(
));
LEA(
, EAX, MScaled(bufwReg, SCALE_4,
));
(
, R(vReg));
ADD(
, R(tempReg1), R(EAX));
AND(
, R(uReg), Imm8(
));
SHR(
, R(uReg), Imm8(
));
MOV(
, R(resultReg), MRegSum(tempReg1, uReg));
FixupBranch skipNonZero = J_CC(CC_NC);
SHR(
, R(resultReg), Imm8(
));
SetJumpTarget(skipNonZero);
AND(
, R(resultReg), Imm8(
));
;
}
示例7: main
add(
,
);
add(
,
);
PRINTMAX(
,
);
PRINTMAX(
,
);
(
,MAXOFNUMBER(
,
));
(
);
sum = ADD(
,
);
(
,sum);
(
,ADD(
,
)*ADD(
,
));
(
,ADD2(
,
)*ADD2(
,
));
(
,
(
,
));
(
,MUL2(
,
));
(
);
(kPath);
r =
;
s = PI*r*r;
c =
*PI*r;
(
,s,c);
;
}
示例8: br_i32_mulacc
void
br_i32_mulacc(uint32_t *d, const uint32_t *a, const uint32_t *b)
{
alen, blen, u;
alen = (a[
] +
) >>
;
blen = (b[
] +
) >>
;
d[
] = a[
] + b[
];
(u =
; u < blen; u ++) {
f;
v;
cc;
f = b[
+ u];
cc =
;
(v =
; v < alen; v ++) {
z;
z = (
)d[
+ u + v] +
(f, a[
+ v]) + cc;
cc = z >>
;
d[
+ u + v] = (
)z;
}
d[
+ u + alen] = (
)cc;
}
}
示例9: gradMagNorm
void gradMagNorm( float *M, float *S, int h, int w, float norm ) {
__m128 *_M, *_S, _norm;
i=
, n=h*w, n4=n/
;
_S = (__m128*) S; _M = (__m128*) M; _norm = SET(norm);
sse = !(
(M)&
) && !(
(S)&
);
(sse)
(; i<n4; i++) { *_M=
(*_M,RCP(ADD(*_S++,_norm))); _M++; }
(sse) i*=
;
(; i<n; i++) M[i] /= (S[i] + norm);
}
示例10: CDD
{
res;
res=firstDigit*
(digitsLeft,NDigits
);
(NDigits==
)
{
(
,firstDigit,number,digitsLeft,NDigits,res);
res+
;
}
(number[
]==
||number[
]==
)
{
firstDigit=
;
}
(number[
]<number[
])
{
firstDigit=number[
]
+
;
}
{
firstDigit=number[
]
;
}
res+=CDD(firstDigit,number+
,digitsLeft
,NDigits
);
(
,firstDigit,number,digitsLeft,NDigits,res);
res;
}
示例11: assert
{
#ifndef NDEBUG
assert(node->type == NT_BINARY_EXPR);
#endif
BinaryExpr *e = (BinaryExpr *)node;
#ifdef LOG_INTERP
logInterpPrefix(parser);
rawlog(
);
((Parser *)parser)->interpDepth++;
#endif
assert(e->lhs && e->rhs);
Value *lhs = e->lhs->interp(e->lhs, parser);
Value *rhs = e->rhs->interp(e->rhs, parser);
(e->op) {
TK_ADD: e->value = ADD(lhs, rhs, e->value);
;
TK_SUB: e->value = SUB(lhs, rhs, e->value);
;
TK_MUL: e->value =
(lhs, rhs, e->value);
;
TK_DIV: e->value = DIV(lhs, rhs, e->value);
;
:
assert(
);
;
}
#ifdef LOG_INTERP
logInterpPrefix(parser);
rawlog(
, token2str(e->op));
((Parser *)parser)->interpDepth--;
#endif
e->value;
}
示例12: main
x =
;
y =
;
x = readInt();
y = readInt();
(
,
(x,y));
;
}
示例13: VECT
Triangle::checkPoint(
Point& point)
{
Point ca = VECT(points[
], points[
]);
Point cd = VECT(points[
], point);
Point ab = VECT(points[
], points[
]);
Point ad = VECT(points[
], point);
Point bc = VECT(points[
], points[
]);
Point bd = VECT(points[
], point);
mul1 =
(ca, cd);
mul2 =
(ab, ad);
mul3 =
(bc, bd);
((SGN(mul1) == SGN(mul2)) &&
(SGN(mul2) == SGN(mul3)) &&
(SGN(mul3) == SGN(mul1)));
}
示例14: SET
// gradientMex('gradientMagNorm',M,S,normConst);
// normalize gradient magnitude at each location (uses sse)
GradientMagnitudeChannel::gradMagNorm(
*M,
*S,
h,
w) {
norm = normalizationConstant;
__m128 *_M, *_S, _norm;
i=
, n=h*w, n4=n/
;
_S = (__m128*) S; _M = (__m128*) M; _norm = SET(norm);
sse = !(
(M)&
) && !(
(S)&
);
(sse) {
(; i<n4; i++) *_M++=
(*_M,RCP(ADD(*_S++,_norm))); i*=
; }
(; i<n; i++) M[i] /= (S[i] + norm);
}
示例15: gradQuantize
void gradQuantize( float *O, float *M, int *O0, int *O1, float *M0, float *M1,
int nb, int n, float norm, int nOrients, bool full, bool interpolate )
{
i, o0, o1;
o, od, m;
__m128i _o0, _o1, *_O0, *_O1; __m128 _o, _od, _m, *_M0, *_M1;
oMult=(
)nOrients/(full?
*PI:PI);
oMax=nOrients*nb;
__m128 _norm=SET(norm), _oMult=SET(oMult), _nbf=SET((
)nb);
__m128i _oMax=SET(oMax), _nb=SET(nb);
_O0=(__m128i*) O0; _O1=(__m128i*) O1; _M0=(__m128*) M0; _M1=(__m128*) M1;
( interpolate )
( i=
; i<=n
; i+=
) {
_o=
(LDu(O[i]),_oMult); _o0=CVT(_o); _od=SUB(_o,CVT(_o0));
_o0=CVT(
(CVT(_o0),_nbf)); _o0=AND(CMPGT(_oMax,_o0),_o0); *_O0++=_o0;
_o1=ADD(_o0,_nb); _o1=AND(CMPGT(_oMax,_o1),_o1); *_O1++=_o1;
_m=
(LDu(M[i]),_norm); *_M1=
(_od,_m); *_M0++=SUB(_m,*_M1); _M1++;
}
( i=
; i<=n
; i+=
) {
_o=
(LDu(O[i]),_oMult); _o0=CVT(ADD(_o,SET(
f)));
_o0=CVT(
(CVT(_o0),_nbf)); _o0=AND(CMPGT(_oMax,_o0),_o0); *_O0++=_o0;
*_M0++=
(LDu(M[i]),_norm); *_M1++=SET(
); *_O1++=SET(
);
}
( interpolate )
(; i<n; i++ ) {
o=O[i]*oMult; o0=(
) o; od=o-o0;
o0*=nb;
(o0>=oMax) o0=
; O0[i]=o0;
o1=o0+nb;
(o1==oMax) o1=
; O1[i]=o1;
m=M[i]*norm; M1[i]=od*m; M0[i]=m-M1[i];
}
(; i<n; i++ ) {
o=O[i]*oMult; o0=(
) (o+
f);
o0*=nb;
(o0>=oMax) o0=
; O0[i]=o0;
M0[i]=M[i]*norm; M1[i]=
; O1[i]=
;
}
}
参考文献链接
https://baijiahao.baidu.com/s?id=1703579021234485096&wfr=spider&for=pc
https://vimsky.com/examples/detail/cpp-ex-----MUL-function.html
https://github.com/joelgallant/skcf
https://github.com/3arbouch/PersonDetection
https://github.com/GodZza/contours
https://github.com/Kalamatee/mesa
https://github.com/DirectFB/mesa
https://github.com/Orphis/ppsspp
https://github.com/WZLpq/XuanIsMe-iOS-Video
https://github.com/Alcaro/Arlib
https://github.com/3arbouch/PersonDetection
https://github.com/Achal-Aggarwal/entire-src
https://github.com/wfwt/jszb
https://github.com/soplist/study-c-cpp
https://github.com/sumboid/qt-problems
https://github.com/gustavofuhr/opencv_dollar_detector
https://github.com/3arbouch/PersonDetection
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
2022-01-07 CPU Cache原理与示例