벡터화¶
병렬 명령어를 찾으려면 트레이서가 메모리 로드/스토어 연산에 대한 충분한 정보를 제공해야 합니다. 이 연산들은 메모리상에서 인접해 있어야 합니다. 이를 위해서는 연산들이 같은 인덱스 변수를 사용하고, 오프셋을 선형 결합 또는 아핀 결합으로 표현할 수 있어야 합니다.
명령줄 플래그:
- –jit vec=1: 표시된 jitdriver(예: NumPyPy 모듈에 있는 것)에 대해 벡터화를 켭니다.
- –jit vec_all=1: 모든 JIT 드라이버에 대해 벡터화를 활성화합니다. 트레이스 필터링 휴리스틱은 매개변수를 참고하십시오.
기능¶
현재 트레이스에 병렬 연산이 포함되어 있으면 다음 연산들을 벡터화할 수 있습니다:
- float32/float64: 덧셈, 뺄셈, 곱셈, 나눗셈, 부호 반전, 절댓값
- int8/int16/int32/int64 산술 연산: 덧셈, 뺄셈, 곱셈, 부호 반전, 절댓값
- int8/int16/int32/int64 논리 연산: and, or, xor
상수 및 변수 확장¶
패킹된 산술 연산은 스칼라 변수나 상수를 벡터 레지스터로 확장합니다.
가드(guard) 강화¶
언롤링된 가드(guard)는 산술적 수준에서 강화됩니다(GuardStrengthenOpt 참조). 그 결과 생성되는 벡터 트레이스에는 인덱스를 검사하는 가드(guard)가 하나만 남습니다.
(병합된 load/store 명령어 때문에) 중복되는 인덱스 변수 계산은 제거되지 않습니다. 이러한 명령어는 백엔드가 트레이스를 어셈블하는 과정에서 제거합니다.
또한 간단한 휴리스틱(–jit vec_all=1로 활성화됨)이 애플리케이션 수준 루프의 배열 경계 검사를 제거하려고 시도합니다. 이 휴리스틱은 배열 경계 검사를 식별하려고 시도하며, 루프의 맨 위에 전이적 가드(guard)를 추가합니다:
label(...)
...
guard(i < n) # index guard
...
guard(i < len(a))
a = load(..., i, ...)
...
jump(...)
# becomes
guard(n < len(a))
label(...)
guard(i < n) # index guard
...
a = load(..., i, ...)
...
jump(...)
향후 과제 및 한계¶
- 현재 지원되는 유일한 SIMD 명령어 아키텍처는 SSE4.1입니다
- int8, int64에 대한 packed mul입니다(PMUL 참조). PCLMULQDQ를 사용할 수도 있을 것입니다. 일부 CPU에서만 지원되므로 cpuid로 확인해야 합니다.
- int(8|16|32|64)에서 int(8|16)으로 타입을 변환하는 루프는 현재 SSE4.1 어셈블러 구현에서 지원되지 않습니다. 여기에 필요한 옵코드는 여러 명령어에 걸쳐 있습니다. 성능 측면에서는 이러한 변환에 SIMD 명령어를 사용해도 이점이 거의 또는 전혀 없을 수 있습니다.
- 벡터 정수 레지스터의 참/거짓을 검사하는 가드(guard)에는 xmm 레지스터 2개(하나는 모든 비트가 0으로 채워지고 다른 하나는 모든 비트가 1로 채워진 것)를 두면 편리합니다. 이렇게 하면 가드(guard) 검사에 필요한 명령어가 2개 줄어드는 대신 레지스터 압박(register pressure)이 높아집니다.
- prod, sum은 64비트 데이터 타입에서만 지원됩니다
- isomorphic 함수는 다음 경우들이 하나의 쌍으로 결합되지 않도록 막습니다: 1) getarrayitem_gc, getarrayitem_gc_pure 2) int_add(v,1), int_sub(v,-1)