std :: bitset보다 c 스타일 비트 조작에 이점이 있습니까?


16

나는 거의 C ++ 11/14에서 독점적으로 일하고 일반적으로 다음과 같은 코드를 볼 때 울다.

std::int64_t mArray;
mArray |= someMask << 1;

이것은 단지 예일뿐입니다. 나는 일반적으로 비트 단위 조작에 대해 이야기하고 있습니다. C ++에서는 실제로 어떤 점이 있습니까? 위의 내용은 마음이 뒤틀리고 오류가 발생하기 쉬운 반면을 사용 std::bitset하면 다음을 수행 할 수 있습니다.

  1. std::bitset템플릿 매개 변수를 조정하고 구현이 나머지를 처리하도록하여 필요에 따라 크기를보다 쉽게 ​​수정합니다.
  2. 무슨 일이 일어나고 있는지 (그리고 실수를 저지르는) 것을 알아내는 데 더 적은 시간을 소비하고 다른 데이터 컨테이너 std::bitset와 유사한 방식으로 작성하십시오 std::array.

내 질문은; 이전 버전과의 호환성을 제외하고 기본 유형을 초과 사용 하지 않는 이유가 std::bitset있습니까?


a의 크기는 std::bitset컴파일 타임에 고정됩니다. 그것이 내가 생각할 수있는 유일한 치명적인 단점입니다.
rwong

1
@ rwong 나는 컴파일 타임에 고정되는 std::bitsetvs c 스타일 비트 조작 (예 :)에 대해 이야기하고 int있습니다.
quant.

한 가지 이유는 레거시 코드 일 수 있습니다. 코드를 std::bitset사용할 수 없거나 작성자에게 알려졌을 때 작성되었으며 사용할 코드를 다시 작성할 이유가 없었습니다 std::bitset.
Bart van Ingen Schenau

개인적으로 "이진 변수의 집합 / 맵 / 배열에 대한 작업"을 쉽게 이해할 수있게하는 방법에 대한 문제는 여전히 해결되지 않은 문제라고 생각합니다. 실제로는 간단한 작업으로 줄일 수없는 많은 작업이 있기 때문입니다. 그러한 집합을 표현하기에는 너무 많은 방법이 bitset있지만 그 중 하나는 작지만 작은 벡터 또는 ints (비트 인덱스) 집합 도 합법적 일 수 있습니다. C / C ++의 철학은 이러한 선택 복잡성을 프로그래머에게 숨기지 않습니다.
rwong

답변:


12

논리적 (기술적이지 않은) 관점에서는 이점이 없습니다.

모든 평범한 C / C ++ 코드는 적합한 "라이브러리 구성"내에 래핑 될 수 있습니다. 이러한 포장 후에, "이것이 그것보다 유리한지"라는 문제는 논쟁의 여지가된다.

C / C ++는 속도 관점에서 라이브러리 구성이 랩핑하는 일반 코드만큼 효율적인 코드를 생성 할 수 있도록해야합니다. 그러나 이것은 다음의 대상이됩니다 :

  • 함수 인라인
  • 컴파일 타임 확인 및 불필요한 런타임 확인 제거
  • 데드 코드 제거
  • 다른 많은 코드 최적화 ...

이러한 종류의 비 기술적 주장을 사용하면 "누락 된 기능"을 누구나 추가 할 수 있으므로 단점으로 간주되지 않습니다.

그러나 추가 코드로는 내장 요구 사항 및 제한 사항을 극복 할 수 없습니다. 아래에서는 크기 std::bitset가 컴파일 타임 상수이므로 단점으로 간주되지는 않지만 여전히 사용자의 선택에 영향을 미치는 것입니다.


미적 관점 (가독성, 유지 보수 용이성 등)에는 차이가 있습니다.

그러나 std::bitset코드가 즉시 일반 C 코드보다 우선 한다는 것은 분명하지 않습니다 . 사용 std::bitset하는 것이 소스 코드의 인간 품질을 향상 시켰 는지 말하기 위해 더 큰 코드 조각 (장난감 샘플이 아닌)을 조사해야 합니다.


비트 조작 속도는 코딩 스타일에 따라 다릅니다. 코딩 스타일은 C / C ++ 비트 조작에 영향을 미치며 std::bitset다음과 같이 적용 할 수 있습니다.


를 사용하여 operator []한 번에 한 비트 씩 읽고 쓰는 코드를 작성 하는 경우 조작 할 비트가 두 개 이상인 경우이 작업을 여러 번 수행해야합니다. C 스타일 코드도 마찬가지입니다.

그러나, bitset또한 다른 통신 사업자, 보유 operator &=, operator <<=등, 비트 세트의 전체 폭에 동작하는한다. 기본 장치는 종종 같은 비트 수의 CPU 주기로 한 번에 32 비트, 64 비트 및 때로는 128 비트 (SIMD 포함)에서 작동 할 수 있으므로 이러한 다중 비트 작업을 활용하도록 설계된 코드 "루프"비트 조작 코드보다 빠를 수 있습니다.

일반적인 아이디어는 SWAR (레지스터 내의 SIMD) 이며 비트 조작의 하위 주제입니다.


일부 C ++ 공급 업체는 bitsetSIMD를 사용하여 64 비트와 128 비트 사이에서 구현할 수 있습니다 . 일부 공급 업체는 그렇지 않을 수도 있지만 결국에는 그렇지 않을 수도 있습니다. C ++ 공급 업체의 라이브러리가 무엇을 수행해야하는지 알아야 할 경우 유일한 방법은 분해를 보는 것입니다.


std::bitset한계 가 있는지에 대해서는 두 가지 예를들 수 있습니다.

  1. std::bitset컴파일시 크기를 알아야합니다. 동적으로 선택된 크기의 비트 배열을 만들려면을 사용해야 std::vector<bool>합니다.
  2. 현재의 C ++ 사양 std::bitset은 더 큰 bitsetM 비트 에서 N 비트의 연속 슬라이스를 추출하는 방법을 제공하지 않습니다 .

첫 번째는 기본입니다. 즉, 동적 크기의 비트 세트가 필요한 사람들은 다른 옵션을 선택해야합니다.

두 번째 어댑터 는 표준을 bitset확장 할 수없는 경우에도 작업을 수행하기 위해 어떤 종류의 어댑터 를 작성할 수 있기 때문에 극복 할 수 있습니다 .


에서 기본적으로 제공되지 않는 특정 유형의 고급 SWAR 작업이 있습니다 std::bitset. 비트 순열에 대한이 웹 사이트의 이러한 작업에 대해 읽을 수 있습니다. 평소와 같이 위에서 구현하여 자체적으로 구현할 수 있습니다 std::bitset.


성능에 관한 토론.

하나의 훈계 : 많은 사람들 이 표준 라이브러리의 (무언가) 단순한 C 스타일 코드보다 훨씬 느린 이유에 대해 묻습니다 . 여기 microbenchmarking의 필수 지식을 반복하지,하지만 난 그냥이 조언을 (최적화가 활성화 된 상태) "릴리스 모드"에서 벤치 마크를 확인하고, 확인 코드가되고 있지 않은지 제거 (죽은 코드 제거) 또는 존재 루프 (루프-불변 코드 모션)에서 들어 올렸습니다 .

일반적으로 우리는 인터넷상에서 누군가가 마이크로 벤치 마크를 올바르게 수행하고 있는지 알 수 없기 때문에 신뢰할만한 결론을 얻을 수있는 유일한 방법은 마이크로 벤치 마크를 작성하고 세부 사항을 문서화하고 공개 검토 및 비평에 제출하는 것입니다. 다른 사람들이 이전에 해왔 던 마이크로 벤치 마크를 다시 실행하는 것은 아프지 않습니다.


문제 # 2는 또한 각 스레드가 비트 세트의 하위 세트에서 작동해야하는 병렬 설정에서 비트 세트를 사용할 수 없음을 의미합니다.
user239558

@ user239558 나는 누군가가 같은 것을 병렬화하기를 원치 않는다 std::bitset. (에서 std::bitset) 메모리 일관성 보장 이 없으므로 코어간에 공유되지 않아야합니다. 여러 코어에서 공유해야하는 사람들은 자체 구현을 구축하는 경향이 있습니다. 서로 다른 코어간에 데이터를 공유 할 때는 캐시 라인 경계에 맞게 정렬하는 것이 일반적입니다. 그렇게하지 않으면 성능이 저하되고 비원 자성 함정이 더 많이 노출됩니다. 의 병렬화 구현을 구성하는 방법에 대한 개요를 제공 할 지식이 충분하지 않습니다 std::bitset.
rwong

데이터 병렬 프로그래밍에는 일반적으로 메모리 일관성이 필요하지 않습니다. 단계간에 만 동기화합니다. 나는 절대적으로 병렬로 비트 세트를 처리하고 싶습니다 bitset. 큰 의지를 가진 사람이 있다고 생각 합니다.
user239558

@ user239558은 복사를 의미합니다 (처리가 시작되기 전에 각 코어에서 처리 할 관련 비트 세트 범위를 복사해야 함). 나는 병렬화에 대해 생각하는 사람은 이미 자신의 구현을 롤아웃하는 것에 대해 생각할 것이라고 생각하지만 동의합니다. 일반적으로 많은 C ++ 표준 라이브러리 기능이 기본 구현으로 제공됩니다. 더 심각한 요구를 가진 사람은 자신의 것을 구현하려고합니다.
rwong

아니 복사가 없습니다. 정적 데이터 구조의 다른 부분에 단순히 액세스하는 것입니다. 그러면 동기화가 필요하지 않습니다.
user239558

2

이것은 모든 경우에 적용되는 것은 아니지만 때로는 알고리즘이 C 스타일의 비트 트위들 링의 효율성에 의존하여 상당한 성능 향상을 제공 할 수 있습니다. 내 마음에 떠오르는 첫 번째 예는 체스 보드 등의 속도를 높이기 위해 비트 보드, 보드 게임 위치의 영리한 정수 인코딩을 사용하는 것입니다. 여기서 체스 판은 항상 8 * 8이므로 정수 유형의 고정 크기는 문제가되지 않습니다.

간단한 예를 들어, 다음 함수 ( Ben Jackson이이 답변 에서 가져옴 )를 사용하여 Connect Four 포지션을 승리로 테스트하십시오.

// return whether newboard includes a win
bool haswon2(uint64_t newboard)
{
    uint64_t y = newboard & (newboard >> 6);
    uint64_t z = newboard & (newboard >> 7);
    uint64_t w = newboard & (newboard >> 8);
    uint64_t x = newboard & (newboard >> 1);
    return (y & (y >> 2 * 6)) | // check \ diagonal
           (z & (z >> 2 * 7)) | // check horizontal -
           (w & (w >> 2 * 8)) | // check / diagonal
           (x & (x >> 2));      // check vertical |
}

2
당신은 std::bitset더 느릴 것이라고 생각 합니까?
quant

음, 소스를 간략히 살펴보면 libc ++ 비트 세트는 단일 size_t 또는 그 배열을 기반으로하므로 특히 sizeof (size_t) == 8- 아니, 아마 더 느리지 않을 것입니다.
Ryan Pavlik 2018
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.