비트 필드를 사용하는 것은 어떤 경우에 가치가 있습니까?
C의 비트필드 구현을 사용할 가치가 있습니까?그렇다면 언제 사용됩니까?
에뮬레이터 코드를 확인했는데 칩 레지스터가 비트 필드를 사용하여 구현되지 않은 것 같습니다.
퍼포먼스상의 이유(또는 다른 이유)로 인해 회피되는 것입니까?
비트필드를 사용하는 경우가 아직 있습니까?(즉, 실제 칩 등에 장착하는 펌웨어)
비트필드는 일반적으로 구조 필드를 특정 비트슬라이스에 매핑해야 할 경우에만 사용됩니다.이 경우 일부 하드웨어는 원시 비트를 해석합니다.예를 들어 IP 패킷헤더를 조립하는 경우가 있습니다.에뮬레이터가 비트필드를 사용하여 레지스터를 모델링해야 하는 설득력 있는 이유를 알 수 없습니다. 실제 하드웨어에는 전혀 영향을 주지 않기 때문입니다.
비트필드는 구문을 깔끔하게 만들 수 있지만 플랫폼에 의존하기 때문에 이식할 수 없습니다.보다 휴대성이 뛰어나지만 보다 상세한 접근방식은 시프트와 비트 마스크를 사용하여 직접 비트 조작을 사용하는 것입니다.
일부 물리 인터페이스에서 구조체의 조립(또는 분해) 이외의 용도로 비트필드를 사용하면 퍼포먼스가 저하될 수 있습니다.이는 사용자가 비트필드에서 읽거나 쓸 때마다 컴파일러가 마스킹과 시프트를 수행하기 위한 코드를 생성해야 하기 때문입니다.그것은 굽기 사이클입니다.
의 한 는 '비트필드'입니다.unsigned
비트필드는 산술모듈로를 "공짜"로 제공합니다.예를 들어 다음과 같습니다.
struct { unsigned x:10; } foo;
으로 표현합니다.foo.x
modulo10 2 = 1024가 수행됩니다.
(해도, 실현할 수 &
물론, 조작은 가능하지만, 경우에 따라서는 컴파일러에 의한 코드의 명확화로 이어질 수 있습니다).
FWIW는 퍼포먼스 관련 질문만 다루고 있습니다.본격적인 벤치마크:
#include <time.h>
#include <iostream>
struct A
{
void a(unsigned n) { a_ = n; }
void b(unsigned n) { b_ = n; }
void c(unsigned n) { c_ = n; }
void d(unsigned n) { d_ = n; }
unsigned a() { return a_; }
unsigned b() { return b_; }
unsigned c() { return c_; }
unsigned d() { return d_; }
volatile unsigned a_:1,
b_:5,
c_:2,
d_:8;
};
struct B
{
void a(unsigned n) { a_ = n; }
void b(unsigned n) { b_ = n; }
void c(unsigned n) { c_ = n; }
void d(unsigned n) { d_ = n; }
unsigned a() { return a_; }
unsigned b() { return b_; }
unsigned c() { return c_; }
unsigned d() { return d_; }
volatile unsigned a_, b_, c_, d_;
};
struct C
{
void a(unsigned n) { x_ &= ~0x01; x_ |= n; }
void b(unsigned n) { x_ &= ~0x3E; x_ |= n << 1; }
void c(unsigned n) { x_ &= ~0xC0; x_ |= n << 6; }
void d(unsigned n) { x_ &= ~0xFF00; x_ |= n << 8; }
unsigned a() const { return x_ & 0x01; }
unsigned b() const { return (x_ & 0x3E) >> 1; }
unsigned c() const { return (x_ & 0xC0) >> 6; }
unsigned d() const { return (x_ & 0xFF00) >> 8; }
volatile unsigned x_;
};
struct Timer
{
Timer() { get(&start_tp); }
double elapsed() const {
struct timespec end_tp;
get(&end_tp);
return (end_tp.tv_sec - start_tp.tv_sec) +
(1E-9 * end_tp.tv_nsec - 1E-9 * start_tp.tv_nsec);
}
private:
static void get(struct timespec* p_tp) {
if (clock_gettime(CLOCK_REALTIME, p_tp) != 0)
{
std::cerr << "clock_gettime() error\n";
exit(EXIT_FAILURE);
}
}
struct timespec start_tp;
};
template <typename T>
unsigned f()
{
int n = 0;
Timer timer;
T t;
for (int i = 0; i < 10000000; ++i)
{
t.a(i & 0x01);
t.b(i & 0x1F);
t.c(i & 0x03);
t.d(i & 0xFF);
n += t.a() + t.b() + t.c() + t.d();
}
std::cout << timer.elapsed() << '\n';
return n;
}
int main()
{
std::cout << "bitfields: " << f<A>() << '\n';
std::cout << "separate ints: " << f<B>() << '\n';
std::cout << "explicit and/or/shift: " << f<C>() << '\n';
}
테스트 머신에서의 출력(실행 횟수는 최대 20%까지 변동):
bitfields: 0.140586
1449991808
separate ints: 0.039374
1449991808
explicit and/or/shift: 0.252723
1449991808
최신 Athlon의 g++-O3에서는 비트필드가 개별 int보다 몇 배 이상 느리고, 이 특정 및/또는 비트시프트 구현이 적어도 두 배 이상 나쁘다는 것을 나타냅니다("메모리 읽기/쓰기 등의 다른 조작이 위의 휘발성에 의해 강조되며, 루프 오버헤드 등이 있기 때문에 그 차이는 회복되지 않습니다."(결과에 기재되어 있습니다.
주로 비트필드 또는 주로 별개의 int일 수 있는 수백 MB의 구조를 취급하는 경우 캐싱 문제가 지배적이 될 수 있습니다. 따라서 시스템에서 벤치마크를 해야 합니다.
2021년부터 AMD Ryzen 9 3900X 및 -O2 -march=blash:
bitfields: 0.0224893
1449991808
separate ints: 0.0288447
1449991808
explicit and/or/shift: 0.0190325
1449991808
모든 것이 크게 변화하고 있는 것을 알 수 있습니다.주요적인 것은, 고객이 관심을 가지는 시스템의 벤치마크입니다.
UPDATE:user2188211은 데이터량이 증가함에 따라 비트필드가 빨라지는 편집을 시도했습니다.「상기 코드의 [수정판]에서 몇 백만 요소의 벡터에 걸쳐 반복할 때, 변수가 캐시 또는 레지스터에 존재하지 않도록 비트필드 코드가 가장 빠를 수 있습니다.」
template <typename T>
unsigned f()
{
int n = 0;
Timer timer;
std::vector<T> ts(1024 * 1024 * 16);
for (size_t i = 0, idx = 0; i < 10000000; ++i)
{
T& t = ts[idx];
t.a(i & 0x01);
t.b(i & 0x1F);
t.c(i & 0x03);
t.d(i & 0xFF);
n += t.a() + t.b() + t.c() + t.d();
idx++;
if (idx >= ts.size()) {
idx = 0;
}
}
std::cout << timer.elapsed() << '\n';
return n;
}
실행 예(g+-03, Core2Duo):
0.19016
bitfields: 1449991808
0.342756
separate ints: 1449991808
0.215243
explicit and/or/shift: 1449991808
물론 타이밍은 모두 상대적인 것으로, 이러한 필드를 어느 방법으로 실장하는지는 시스템의 맥락에서 전혀 문제가 되지 않을 수 있습니다.
비트필드의 주된 목적은 데이터의 보다 긴밀한 패킹을 실현함으로써 대규모 인스턴스화된 집약 데이터 구조에서 메모리를 절약할 수 있는 방법을 제공하는 것입니다.
즉, 표준 데이터 유형의 전체 너비(및 범위)가 필요 없는 구조 유형에 여러 개의 필드가 있는 상황을 활용하는 것입니다.이렇게 하면 이러한 여러 필드를 하나의 할당 단위로 묶을 수 있으므로 구조 유형의 전체 크기를 줄일 수 있습니다.를 들 수 필드는 의 비트를 1개의 (예를 32비트)로할 수 있습니다.unsigned int
★★★★★★★★★★★★★★★★★★」
이는 메모리 소비 감소의 장점이 비트필드에 저장된 값에 대한 접근 속도가 느리다는 단점보다 큰 경우에만 의미가 있습니다.단, 이러한 상황은 매우 자주 발생하므로 비트필드는 절대적으로 필수적인 언어 기능입니다.이는 비트필드의 현대적인 사용에 대한 질문에 대한 답변입니다.비트필드는 사용될 뿐만 아니라 대량의 동종 데이터(예를 들어 큰 그래프 등)를 처리하는 데 중점을 둔 실질적으로 의미 있는 코드에서 필수적입니다.이는 메모리 절약의 이점이 개별 액세스 성능의 단점보다 훨씬 크기 때문입니다.es.
산술형,즉 '' 산술형과 합니다.signed/unsigned char
,short
,float
. 으로 실데음음음음음 than음 than음 than음 than smaller than smaller than smaller than smaller than than than than than than than than than than than than than than than than than smaller smaller smaller smaller 보다 작은 것은 사용하지 않습니다int
★★★★★★★★★★★★★★★★★」double
(일부러)다음과 같은 산술적 유형signed/unsigned char
,short
,float
「스토리지」타입으로서 기능하기 위해서만 존재합니다.즉, 범위(또는 정밀도)가 충분한 상황에서, 구조 타입의 메모리를 절약하는 컴팩트한 부재로 사용됩니다.비트필드는 같은 방향의 또 다른 단계로, 메모리 절약의 이점을 위해 조금 더 높은 성능을 제공합니다.
따라서 비트필드를 사용할 가치가 있는 조건을 명확하게 할 수 있습니다.
- 구조 유형에는 더 적은 수의 비트로 패킹할 수 있는 여러 필드가 포함되어 있습니다.
- 프로그램은 해당 구조 유형의 많은 개체를 인스턴스화합니다.
조건이 충족되면 모든 비트패커블필드를 연속적으로 선언하고(통상은 구조체 타입의 마지막에), 적절한 비트폭을 할당합니다(통상은 비트폭이 적절한 것을 확인하기 위한 몇 가지 절차를 수행합니다).대부분의 경우 최적의 패킹 및/또는 성능을 얻기 위해 이러한 필드를 정렬하는 것이 좋습니다.
또한 비트필드는 하드웨어 레지스터, 부동소수점 형식, 파일 형식 등 외부에서 지정된 다양한 표현으로 비트그룹을 매핑하기 위해 사용됩니다.어떤 이유로 인해 이러한 종류의 비트필드 오용은 실제 코드에서 계속 팝업되지만 이는 비트필드의 적절한 사용으로 의도된 것은 아닙니다.그냥 이러지 마세요.
비트 필드는 다음 두 가지 상황에서 확인/사용했습니다.컴퓨터 게임 및 하드웨어 인터페이스.하드웨어의 사용은 매우 간단합니다.하드웨어는 수동으로 정의할 수도 있고 미리 정의된 라이브러리 구조를 통해 특정 비트 형식의 데이터를 요구합니다.비트 필드를 사용하는지 비트 조작만 사용하는지는 특정 라이브러리에 따라 달라집니다.
"옛날" 컴퓨터 게임은 컴퓨터/디스크 메모리를 최대한 활용하기 위해 비트 필드를 자주 사용했습니다.예를 들어, RPG 의 NPC 정의의 경우는, 다음의 예를 참조할 수 있습니다.
struct charinfo_t
{
unsigned int Strength : 7; // 0-100
unsigned int Agility : 7;
unsigned int Endurance: 7;
unsigned int Speed : 7;
unsigned int Charisma : 7;
unsigned int HitPoints : 10; //0-1000
unsigned int MaxHitPoints : 10;
//etc...
};
컴퓨터의 메모리가 늘어남에 따라 공간 절약이 비례적으로 나빠지기 때문에 최신 게임/소프트웨어에서는 거의 볼 수 없습니다.컴퓨터의 메모리가 16MB밖에 없을 때 1MB의 메모리를 절약하는 것은 큰 일이지만, 4GB일 때는 그리 많지 않습니다.
비트 필드의 한 가지 용도는 임베디드 코드를 쓸 때 하드웨어 레지스터를 미러링하는 것이었습니다.단, 비트 순서는 플랫폼에 의존하기 때문에 하드웨어가 프로세서와 다른 비트 순서를 지정하면 동작하지 않습니다.그러나 비트 필드의 용도는 더 이상 생각할 수 없습니다.플랫폼 간에 이식할 수 있는 비트 조작 라이브러리를 구현하는 것이 좋습니다.
70년대에는 비트필드를 사용하여 trs80의 하드웨어를 제어했습니다.디스플레이/키보드/카세트/디스크는 모두 메모리 맵 디바이스였다.각각의 비트가 여러 가지를 통제했다.
- 비트 제어된 32컬럼 대 64컬럼 표시
- 동일한 메모리 셀의 비트 0은 카세트 직렬 데이터 입력/출력입니다.
내 기억으로는, 디스크 드라이브 컨트롤에 여러 개가 있었어.총 4바이트였습니다.2비트 드라이브 셀렉트가 있었던 것 같아요.하지만 그것은 오래 전이었다.플랫폼용 c 컴파일러가 적어도2개 있다는 것은 당시로서는 인상적이었습니다.
다른 관찰로는 비트필드는 실제로 플랫폼에 고유합니다.비트 필드가 있는 프로그램이 다른 플랫폼으로 포트될 가능성은 없습니다.
고려해야 할 대안은 각 바이트가 비트를 나타내는 더미 구조(인스턴스화되지 않음)를 가진 비트필드 구조를 지정하는 것입니다.
struct Bf_format
{
char field1[5];
char field2[9];
char field3[18];
};
이 접근법에서는 size of는 비트필드의 폭을 나타내고 offsetof는 비트필드의 오프셋을 나타냅니다.적어도 GNU gcc의 경우, (끊임없는 시프트와 마스크에 의한) 비트 단위 연산의 컴파일러 최적화는 (기본 언어) 비트 필드와 대략적인 패리티에 도달한 것으로 보인다.
C++ 헤더 파일(이 접근방식을 사용하여)을 작성했습니다.이 파일에서는 비트필드의 구조를 퍼포먼스, 휴대성, 유연성이 뛰어난 방법으로 정의하고 사용할 수 있습니다.https://github.com/wkaras/C-plus-plus-library-bit-fields.따라서 C를 사용할 필요가 없는 한 기본 언어 기능을 사용할 이유가 거의 없다고 생각합니다.r 비트 필드
비트 필드는 옛날에는 프로그램 메모리를 절약하기 위해 사용되었습니다.
레지스터는 사용할 수 없기 때문에 성능을 저하시킵니다.그래서 이 레지스터를 사용하기 위해서는 정수로 변환해야 합니다.이러한 코드들은 더 복잡한 코드로 이어지기 쉽고 이해하기 어렵습니다(이 값을 실제로 사용하려면 항상 사물을 마스킹 및 마스크 해제해야 하기 때문입니다).
모든 비트필드 glority의 pre ansi c를 확인하려면 , http://www.nethack.org/ 의 소스를 확인해 주세요.
현대의 코드에서는 비트필드를 사용하는 이유는 단 하나뿐입니다.즉, 데이터 베이스의 공간 요건을 제어하기 위해서입니다.bool
또는enum
유형, 구조체/클래스 내.예: (C++):
enum token_code { TK_a, TK_b, TK_c, ... /* less than 255 codes */ };
struct token {
token_code code : 8;
bool number_unsigned : 1;
bool is_keyword : 1;
/* etc */
};
IMO를 사용하지 않을 이유는 기본적으로 없습니다.:1
비트 필드bool
최신 컴파일러는 매우 효율적인 코드를 생성합니다.하지만 C에서는, 반드시,bool
typedef는 C99 중 하나입니다._Bool
서명되지 않은 int에 실패하는 경우도 있습니다.서명된 1비트필드는 값만 유지할 수 있기 때문입니다.0
그리고.-1
(무엇이든 비투스 어카운트 머신이 있는 경우는 제외).
열거형에서는 비효율적인 코드 생성(보통 읽기-수정-쓰기 사이클)을 피하기 위해 항상 원시 정수형(8/16/32/64비트) 중 하나에 해당하는 크기를 사용하십시오.
비트필드를 사용하여 외부에서 정의된 데이터 형식(패킷헤더, 메모리 매핑된 I/O 레지스터)을 사용하여 구조를 정렬하는 것이 일반적으로 권장되지만, 실제로는 C가 엔디안성, 패딩 및 (I/O reg의 경우) 정확히 어떤 어셈블리시퀀스가 방출되는지를 충분히 제어할 수 없기 때문에 잘못된 관행이라고 생각합니다.이 영역에서 C가 얼마나 누락되었는지 알고 싶다면 Ada의 표현 조항을 참조하십시오.
Boost.Thread는 비트필드를 사용합니다.shared_mutex
Windows 에서는, 이하를 참조해 주세요.
struct state_data
{
unsigned shared_count:11,
shared_waiting:11,
exclusive:1,
upgrade:1,
exclusive_waiting:7,
exclusive_waiting_blocked:1;
};
언급URL : https://stackoverflow.com/questions/4240974/when-is-it-worthwhile-to-use-bit-fields
'programing' 카테고리의 다른 글
Vuex 속성 값을 변환이 아닌 액션 내부에서 설정하는 것이 잘못되었습니까? (0) | 2022.08.01 |
---|---|
완벽한 JPA 엔티티 생성 (0) | 2022.08.01 |
부호 없는 데이터 유형은 무엇입니까? (0) | 2022.08.01 |
VueJs 라디오 버튼을 통한 심플한 토굴링 (0) | 2022.08.01 |
==와 Integer.valueOf(String)의 비교 결과가 127과 128에 대해 다른 이유는 무엇입니까? (0) | 2022.08.01 |