您是否有理由不使用访问器功能?
有两种基本情况:结构化数据和纯数据。结构化数据具有混合数据类型,纯数据只有您列出的一种格式。如果您可以为每种使用的类型(列出的所有类型总共 28 或 30 个字节)存储一个原型值(具有不同的字节组件),您还可以支持透明的字节序校正——复杂类型只是对,并且具有相同的字节顺序作为基本组件)。我已经使用这种方法来存储和访问来自分子动力学模拟的原子数据,并且在实践中非常有效——这是我测试过的最快的便携式方法。
我会使用一个结构来描述“文件”(支持文件、内存映射、字节顺序和数据格式,如果是纯数据):
struct file {
int descriptor;
size_t size;
unsigned char *data;
unsigned int endian; /* Relative to current architecture */
unsigned int format; /* endian | format, for unstructured files */
};
#define ENDIAN_I16_MASK 0x0001
#define ENDIAN_I16_12 0x0000
#define ENDIAN_I16_21 0x0001
#define ENDIAN_I32_MASK 0x0006
#define ENDIAN_I32_1234 0x0000
#define ENDIAN_I32_4321 0x0002
#define ENDIAN_I32_2143 0x0004
#define ENDIAN_I32_3412 0x0006
#define ENDIAN_I64_MASK 0x0018
#define ENDIAN_I64_1234 0x0000
#define ENDIAN_I64_4321 0x0008
#define ENDIAN_I64_2143 0x0010
#define ENDIAN_I64_3412 0x0018
#define ENDIAN_F16_MASK 0x0020
#define ENDIAN_F16_12 0x0000
#define ENDIAN_F16_21 0x0020
#define ENDIAN_F32_MASK 0x00C0
#define ENDIAN_F32_1234 0x0000
#define ENDIAN_F32_4321 0x0040
#define ENDIAN_F32_2143 0x0080
#define ENDIAN_F32_3412 0x00C0
#define ENDIAN_F64_MASK 0x0300
#define ENDIAN_F64_1234 0x0000
#define ENDIAN_F64_4321 0x0100
#define ENDIAN_F64_2143 0x0200
#define ENDIAN_F64_3412 0x0300
#define FORMAT_MASK 0xF000
#define FORMAT_I8 0x1000
#define FORMAT_I16 0x2000
#define FORMAT_I32 0x3000
#define FORMAT_I64 0x4000
#define FORMAT_P8 0x5000 /* I8 pair ("complex I8") */
#define FORMAT_P16 0x6000 /* I16 pair ("complex I16") */
#define FORMAT_P32 0x7000 /* I32 pair ("complex I32") */
#define FORMAT_P64 0x8000 /* I64 pair ("complex I64") */
#define FORMAT_R16 0x9000 /* BINARY16 IEEE-754 floating-point */
#define FORMAT_R32 0xA000 /* BINARY32 IEEE-754 floating-point */
#define FORMAT_R64 0xB000 /* BINARY64 IEEE-754 floating-point */
#define FORMAT_C16 0xC000 /* BINARY16 IEEE-754 complex */
#define FORMAT_C32 0xD000 /* BINARY32 IEEE-754 complex */
#define FORMAT_C64 0xE000 /* BINARY64 IEEE-754 complex */
访问器功能可以以各种方式实现。在 Linux 中,标记static inline
的函数也与宏一样快。
由于该double
类型不完全涵盖 64 位整数类型(因为尾数只有 52 位),我将定义一个数字结构,
#include <stdint.h>
struct number {
int64_t ireal;
int64_t iimag;
double freal;
double fimag;
};
并让访问器函数始终填写四个字段。使用 GCC,您还可以创建一个宏来使用自动类型检测定义结构编号:
#define Number(x) \
( __builtin_types_compatible_p(__typeof__ (x), double) ? number_double(x) : \
__builtin_types_compatible_p(__typeof__ (x), _Complex double) ? number_complex_double(x) : \
__builtin_types_compatible_p(__typeof__ (x), _Complex long) ? number_complex_long(x) : \
number_int64(x) )
static inline struct number number_int64(const int64_t x)
{
return (struct number){ .ireal = (int64_t)x,
.iimag = 0,
.freal = (double)x,
.fimag = 0.0 };
}
static inline struct number number_double(const double x)
{
return (struct number){ .ireal = (int64_t)x,
.iimag = 0,
.freal = x,
.fimag = 0.0 };
}
static inline struct number number_complex_long(const _Complex long x)
{
return (struct number){ .ireal = (int64_t)(__real__ (x)),
.iimag = (int64_t)(__imag__ (x)),
.freal = (double)(__real__ (x)),
.fimag = (double)(__imag__ (x)) };
}
static inline struct number number_complex_double(const _Complex double x)
{
return (struct number){ .ireal = (int64_t)(__real__ (x)),
.iimag = (int64_t)(__imag__ (x)),
.freal = __real__ (x),
.fimag = __imag__ (x) };
}
这意味着只要是整数或浮点实数或复数类型,就可以Number(value)
构造一个正确的类型。struct number
value
请注意整数和浮点组件如何设置为相同的值,只要类型转换允许。(对于数量级非常大的整数,浮点值将是一个近似值。(int64_t)round(...)
在设置整数分量时,您还可以使用舍入而不是截断浮点参数。
您将需要四个访问器函数:两个用于结构化数据,两个用于非结构化数据。对于非结构化(纯)数据:
static inline struct number file_get_number(const struct file *const file,
const size_t offset)
{
...
}
static inline void file_set_number(const struct file *const file,
const size_t offset,
const struct number number)
{
...
}
注意offset
上面不是字节偏移,而是数字的索引。对于结构化文件,您需要使用字节偏移量,并添加一个指定文件中使用的数字格式的参数:
static inline struct number file_get(const struct file *const file,
const size_t byteoffset,
const unsigned int format)
{
...
}
static inline void file_set(const struct file *const file,
const size_t byteoffset,
const unsigned int format,
const struct number number)
{
...
}
我省略的函数体中所需的转换 (...) 非常简单。您也可以采取一些技巧进行优化。例如,我喜欢调整字节序常数,使低位始终是字节交换(ab -> ba,abcd -> badc,abcdefgh -> badcfehg),高位是短交换(abcd -> cdab , abcdefgh->cdabghef)。如果您想完全确定,您可能需要第三位来表示 64 位值(abcdefgh -> efghabcd)。
函数体中的 if 或 case 语句确实会导致很小的访问开销,但它应该足够小,可以在实践中忽略。避免它的所有方法都会导致更复杂的代码。(为了获得最大吞吐量,您需要对所有访问变体进行开放编码,并__builtin_types_compatible_p()
在函数或宏中使用以确定要使用的正确方法。如果考虑字节序转换,这意味着很多函数。我相信非常小的访问开销——每次访问最多几个时钟——更可取。(无论如何,我所有的测试都是 I/O 绑定的,即使是 200 Mb/s,所以对我来说开销完全无关紧要。)
通常,对于使用原型值的自动字节顺序转换,您只需测试该类型的每个可能的转换。只要原型值的每个字节分量都是唯一的,那么只有一次转换会产生正确的预期值。在某些架构上,整数和浮点值具有不同的字节序;这就是为什么ENDIAN_
常量分别用于每种类型和大小的原因。
假设您已经实现了上述所有内容,那么在您的应用程序代码中,数据访问将类似于
struct file f;
/* Set first number to zero. */
file_set_number(&f, 0, Number(0));
/* Set second number according to variable v,
* which can be just about any numeric type. */
file_set_number(&f, 1, Number(v));
希望这个对你有帮助。