별도의 명시가 없는 한, 본 글의 모든 그림은 위 논문에서 가져왔습니다.

1. Instruction

1.0. Overview

  • OLAP 과 같은 시스템에서는 보통 columnar data format 을 많이 이용한다. 그 이유는:
    • Row data 를 load 하면 불필요한 column 까지 load 되기 때문
    • Columnar data 는 compression 이 용이해 보통 데이터의 크기가 더 작기 때문

1.0.1. Vectorized execution

  • Chunk (Vector) 단위로 query execution 을 처리하는 것을 의미한다.
    • 여기서 Chunk (Vector) 는 하나의 데이터 (single row) 가 아닌 여러개의 데이터 (multiple rows) 를 의미한다.
    • 보통 1024 개를 묶어 하나의 vector 라고 한다.
  • 가령 loop 을 돌며 어떤 작업 (func) 을 하는 다음의 코드는,
int arr[MAX];
 
void func(int i) {
	// do something...
}
 
for (int i = 0; i < MAX; i++){
	func(arr[i]);
}
  • 이것 대신
int arr[MAX];
 
void func(int* vec, int begin) {
	for (int i = begin; i < begin + 1024; i++) {
		// do something...
	}
}
 
for (int i = 0; i < MAX; i += 1024) {
	func(arr, i);
}
  • 이렇게 하는 방식이다.
  • 위 두 방식은 동일하지만, vectorized execution 하는 것이 더 좋다. 왜냐면:
    • 일단 function call 이 적어지기 때문에 function call overhead 를 줄일 수 있고,
    • 최적화하기도 좋다.
      • Function 내에서의 loop 횟수가 정해져있고 1
      • 단순 작업을 looping 하는 것이 해당 함수의 전부이기 때문에 SIMD 를 사용하기에도 용이하다.

1.0.2. Vectorized decoding

  • 위와 같은 vectorized execution 은 decompression 에도 적용하여 이점을 볼 수 있다. (즉, Vectorized decoding)
  • 또한 uncompressed 상태의 vector 크기를 L1 혹은 L2 캐시에 맞춘다면 decompression 이후 memory 에 저장하지 않고 캐시에서 바로 가져다가 query execution 을 할 수 있으므로 memory 에는 compressed vector 만 저장되게 되어 memory 사용량 또한 줄일 수 있다.

1.0.3. Parquet

  • Parquet 에서는 무조건 DICT 를 적용하고, 이렇게 해서 생성된 code sequence 에 대해서는 RLEBP 를 적용한다.
  • 하지만 이때 RLEBP 는 데이터의 사이즈가 가변적 2 이고,

1.0.4. Compressed execution

  • Compressed Query Execution 은 데이터가 compressed 된 상태에서 그것을 decompression 하지 않고 (혹은 processing 가능한 최소의 크기로만 부분적으로 decompression 하여) query processing 을 하는 것을 일컫는다.
    • “부분적” 이라는 것은 가령 8bit 로 compressed 된 데이터가 원래는 64bit 로 decompression 되어야 하지만 16bit 로만 decompression 해도 processing 이 가능하다면 그렇게 하는 것이라 생각하면 된다.
  • DuckDB 와 같은 근래의 DBMS 들은 이러한 compressed vector (random access 가 가능한 compressed data) 를 지원한다.
    • 가령 FOR-vector 의 경우 vector 의 1024 개의 value 들은 uint8 로 compression 되어 있고, 마지막에 base 를 위한 uint64 값 하나만 더 추가하는 식으로 구현할 수 있다.
    • 마찬가지로 DICT-vector 의 경우 각 code 는 uint8 이되 dictionary 를 가리키는 pointer 하나만 uint64 로서 구현할 수 있다.
  • 이렇게 함으로써 SIMD 와 같은 optimization 도 사용할 수 있게 되었고, (SIMD 에서는 작은 사이즈 데이터 여러개를 레지스터에 넣으니깐은) 캐시나 메모리 사용량도 더 줄일 수 있게 되었다.
  • 또한 compressed vector 를 “부분적으로” decompress 하여 가능한 최소 사이즈의 lane-width (processing size 라고 이해하자) 가 되게 하는 best-case scan decoding 을 best-case 를 넘어 common-case 가 되게 했다고 한다.

1.0.5. FastLanes

  • FastLanes 는 (저자의 소속인) CWI 에서 시작한 next-generation bigdata format 이다.
  • 이것은 parallel processing 의 기회를 최대한 늘린 columnar compression layout 으로,
    • 다양한 Instruction Set Architecture (ISA) 에 대응할 수 있고,
      • 즉, 특정 architecture 에 의존적이지 않고 공통적으로 지원하는 instruction 에만 의존하고
    • Scalar-only code 를 사용하여 vectorized execution 을 사용하지 않고도 효과적 3 이다.

1.1. Challenges and Contributions

  • FastLanes 의 핵심은 데이터의 의존성을 최대한 줄여 parallel processing 을 최대로 활용할 수 있게 한다는 것에 있다. 이때 당면한 문제들 (Challeges) 와 그에 대한 해결 방법 (Contribution) 들을 알아보자.
  • 요약하자면:

CHALLENGECONTRIBUTION
SIMD register 크기가 다양함1024bit 크기의 가상 register FLMM1024 를 가정
다양한 SIMD ISA 가 있음모든 SIMD ISA 가 공통적으로 제공하는 instruction 들로 FLMM1024 에 대한 instruction 을 정의
(DELTA) 데이터 간의 의존성이 있음Transposing: column 내 데이터들의 순서를 바꿔 이런 의존성을 무력화함
기존의 scheme 들은 특정 lane-width 에만 효과적임Unified Transposed Layout: 모든 lane-width 에 적용할 수 있는 scheme
Code portability: 특정 ISA 에서만 사용할 수 있는 코드여서는 안됨Scalar code 를 작성, vectorization 은 intrinsic 을 사용하는 것이 아닌 compiler 에게 맡기는 방식
LOAD, STORE boundMemory 에는 compressed data 만 유지, cache 에 들어갈 정도의 크기까지만 decompress 하여 cache 된 데이터가 바로 execition 될 수 있도록 함 (reduce LOAD-bound) + BP fusing 으로 STORE-bound 해결

1.1.1. Many SIMD widths

  • 지난 25년동안 SIMD 레지스터 크기는 8배나 증가했다. 이에 따라 현 시점에서 가장 큰 사이즈의 SIMD 레지스터 크기에 맞추는 것이 아닌, 추후에 더 증가할 것이라는 예측에 따라 1024bit 의 가상 SIMD 레지스터 FLMM1024 를 사용하여 FastLanes 를 설계했다고 한다.
    • 이건 기존의 어떤 ISA 보다도 성능이 좋고, scalar code 보다도 좋다고 한다. 4
  • Bit 레벨에서는, BPinterleaving 해서 128 개의 8bit lane (즉, 총 1024bit) 에 맞춘다고 한다.
    • 가령 3bit value 들을 8bit lane 에 round robin 으로 할당하는거지
  • Implementation 레벨에서는, 1024-value vector 를 한꺼번에 처리 5 하여 vector 하나 처리하는 데에 어떤 경우에는 17 cycle 만에도 가능했다고 한다.

1.1.2. Heterogeneous ISAs

  • SIMD instruction 들은 ISA 마다도 다르지만, ISA 내에서도 generation 에 따라서도 다르다.
    • 가령 x86 에서 SSE 와 AVX 가 다른것처럼
  • 따라서 이런 다양성을 무마하기 위해, FLMM1024 레지스터에 대한 간단한 instruction 또한 정의했다고 한다.
    • 당연히 이 instruction 들은 다양한 ISA 들이 제공하는 instruction 들의 intersection 일것이다.
  • 본 논문에서는 자세하게 언급하지는 않았지만, FLMM1024 instruction 들은 GPU 나 TPU 와 같은 다른 accelerator 들과의 호환 가능성도 제시한다.

1.1.3. Decoding dependencies

  • RLEcontrol dependency 가 있다.
    • 이 control dependency 에 대해서는 딱히 어떤 해결책을 내놓지는 않는 것 같다.
  • 그리고 DELTA 에게는 data dependency 가 있다.
    • DELTA 의 경우에는 바로 이전의 값을 알아야 되기 때문.
    • 근데 SIMD 를 사용하게 되면 이 이전의 값이 바로 옆의 lane 에 위치하게 되는데
    • Lane-dependency (옆 lane 의 값을 가져다 사용하는 것) 의 경우에는 훨씬 더 오래걸리기 때문에 다른 격리방법을 사용해야 한다.
  • FastLanes 에서는 column data 의 순서를 바꿔 data dependency 를 해결하는 Transposing 기법을 제시한다.
  • 그리고 DELTADICT 의 조합에 RLE 을 다시 매핑해서 훨씬 효율적인 DELTA encoding 을 한다고 한다 6.

1.1.4. Layouts that depend on lane-width

  • 기존의 연구들은 하나의 data stream (예를 들어 column 하나) 에 대한 encoding 만을 고려했고, 따라서 특정 lane width 에서만 최적의 성능을 보여줬다.
  • 하지만 여러 column 들은 각기 다른 data distribution 을 가질 수 있기에, lane width 또한 다양해질 수 있다.
    • BP 의 bit width 가 다양할 수 있기 때문에
  • 따라서 가능한 모든 lane width 에 대응할 수 있는 방법이 필요하다.
  • 또한 저자가 말하는 Transposing 을 사용할 때는 각 column 들의 순서가 달라질 수 있다.
    • 단순하게 생각하면 column A 는 오름차순, column B 는 내림차순인 등
    • 근데 이렇게되면 안되자나?
  • 그래서 위의 두 문제 7 (모든 가능한 lane width 에 대응 + column 들의 reordering 순서 통일) 를 해결하기 위한 것이 Unified Transposed Layout 이다.
  • 여기서는 1024개의 값을 16개씩 8묶음으로 만들어서 0-4-2-6-1-5-3-7 순서로 나열한다.
  • 이렇게 하는 것이 왜 문제를 해결하는지는 뒤에서 배운다.

1.1.5. Keeping code portable

  • 저자가 소개하는 FLMM1024 instruction set 은 단순하게 설계되어있기 때문에 uint64 레지스터와 연산으로 저것들을 구현 (시뮬레이션?) 할 수 있다고 한다.
  • 이러한 portability (특정 ISA 에 구애받지 않음) 는 SIMD 를 지원하지 않고 32bit 연산을 지원하는 매우 저사양의 CPU 에서도 FastLanes 를 사용했을 떄 성능 향상을 이뤄낼 수 있다고 한다.
  • 일반적인 64bit ISA 에서는, SIMD 를 사용하지 않는 scalar code 의 경우에도 lane width 가 작을 때 SIMD 와 유사한 acceleration 이 일어났다고 한다.
  • 즉, independency 를 늘려 SIMD 에 친화적으로 설계하는 것이 SIMD 를 사용하는 것 뿐만 아니라 사용하지 않았을 때에도 성능향상이 있었다고 한다.
  • 또한 근래의 compiler 들은 optimizing 과정에서 SIMD 와 같은 vectorized execution 을 활용하게 되고, 따라서 FastLanes 는 별도의 SIMD intrinsic 을 사용하지 않아도 컴파일 과정에서 arch 가 지원하는 SIMD instruction 으로 컴파일된다 - 즉, high-level code 에는 특정 ISA 가 특정되어 있지 않기 때문에 그만큼 유연성을 가지는 것

1.1.6. Avoid getting LOAD, STORE bound

  • FastLanes 에서 compressed data 는 memory 에 들어오고, 그것을 decompression 한 것은 CPU 의 L1 캐시에도 들어갈 정도로 작기 때문에 바로 query execution 을 위한 pipeline 으로 진입할 수 있도록 설계되어 있다.
  • 따라서 CPU-memory 간 traffic 을 compression ratio 만큼이나 줄일 수 있게 된다.
  • 이에 따라 대부분의 CPU time 은 query execution 에 사용되기 때문에, CPU-memory 대역폭도 덜 사용할 수 있게 된다.
  • 여기에 sequential scan 의 경우 prefetching 까지 결부되어 더욱 좋은 성능을 보여준다.
  • 정리하자면, 이 모든것들은 결국 LOAD instruction 을 적게 사용하게 되어 부담을 줄이게 된다 (reduce LOAD-bound)
  • 하지만 decoding 이 너무 빨라 연산의 결과를 다시 저장하는 (STORE) 것에 부담 (STORE-bound) 이 생기게 된다.
  • 이를 위해 FOR, DELTA, RLE, DICT 에다가 BP 를 fusing 하여 STORE+LOAD bound 를 줄였다고 한다 8.

1.2. Outline

  • Section 2 에서는 FastLanes 의 구체적인 logic 을 살펴본다. 다음의 세 파트로 이루어져 있다.
    1. 우선 1024-bit interleaved bit-packing
    2. DELTA 의 dependency 를 깨기 위한 Unified Transposed Layout
    3. 위의 두 방법을 활용한 RLE decoding
  • Section 3 에서는 decompression performance evaluation 을 하며
  • Section 4 에서는 기존의 방법들과 FastLane 의 차이점에 대해 알아보고
  • Section 5 에서 마무리 짓는 형식이다.

Footnotes

  1. 주인장의 추측이다.

  2. 여기서 variable sized 라는 것이 뭔지 잘 모르겠음; BP 는 bit width 가 가변적이라고 받아들일 수 있는데 RLE 는 뭐가 가변적이라는거고

  3. Vectorized execution 을 사용하지 않아도 좋다는 것이 맞는 해석인지 모르겠음.

  4. 더 안좋은 것에 base 를 맞춰야 하는 것 아닌가? virtual 이 actual 보다 더 좋다면 actual performance 는 virtual 일때보다 evaluation 결과가 더 안좋을텐데 그럼 논리가 이상한데

  5. 아니 ㅅㅂ 뭐라는거야 128개의 8bit lane 이면 한번에 128 개씩 처리하는거 아닌가

  6. 뭔소리임?

  7. 원문에서는 이것이 별도의 문제가 아니고 하나의 흐름으로 연결되어 하나의 문제처럼 나온다. 근데 아직 감은 안온다.

  8. 아직 이게 뭔지는 모른다.