如何使用位域成员将 类 的 `swap` 专门化?
How to specialize `swap` for classes with bitfield members?
假设我有一个 C++ 结构:
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
};
我希望能够尽快交换此 class 的元素。只调用 std::swap(cl1, cl2)
还是专门化更好,如何?这行得通吗?
Clazz:swap(Clazz& other) {
std::swap(a, other.a);
std::swap(b, other.b); // how to make C++ swap the whole uint8_t value at once?
}
我希望足够的编译器在没有你提供交换的情况下正确地完成它,所以你应该衡量什么会更快但是你可能想尝试的一件事是转换为 uint8_t
void Clazz::swap(Clazz& other) {
std::swap(reinterpret_cast<uint8_t&>(*this), reinterpret_cast<uint8_t&>(other))
}
或者使用 memcpy
重写交换
好的,现在去编译代码吧。已使用 -O2 优化级别的 gcc5.2 进行测试:
void test1(Clazz& a, Clazz& b) {
a.swap(b);
}
void test2(Clazz& a, Clazz& b) {
std::swap(a, b);
}
生成的代码:
test1(Clazz&, Clazz&):
movzbl (%rdi), %eax
movzbl (%rsi), %edx
movb %dl, (%rdi)
movb %al, (%rsi)
ret
test2(Clazz&, Clazz&):
movzbl (%rdi), %eax
movzbl (%rsi), %edx
movb %dl, (%rdi)
movb %al, (%rsi)
ret
假设您有以下代码:
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
};
Clazz c1,c2;
c1.a = 1; c1.b = 61;
c2.a = 3; c2.b = 63;
您可以在 class 中只执行 std::swap(c1, c2)
而无需额外的交换功能,事情就会按预期进行交换。请注意,结果可能会因实施和粗略的性能测试而异() shows that a custom swap (in the case of this code) may be more optimal. Thanks to user Nax's ,我在下面编辑了我的测试代码以考虑更大的集合。
当下面的测试代码在没有优化的情况下在 g++ 4.2.1
中编译时,std::swap
循环的粗略平均值变为 0.00523261 us
而 Clazz:swap
粗略平均值变为至 0.00996584 us
。这意味着 std::swap
实现比 reinterpret_cast
将 Clazz
对象转换为 uint8_t
更快,但是,当您打开最大优化时(-O3
),时间更加明显,std::swap
循环进入 0.00102523 us
,Clazz::swap
循环进入 0.000739868 us
,速度显着提高 std::swap
方法。
此外,如果您的 Clazz
对象变得比默认构造函数更复杂,以至于您必须包含一个复制构造函数,那么 std::swap
的时间将几乎翻倍因为大多数实现将在创建临时对象时使用对象的复制构造函数。向 Clazz
对象添加一个空的默认构造函数和一个复制构造函数(即 Clazz(const Clazz& cp) : a(cp.a), b(cp.b) {}
)将 std::swap
次更改为以下内容:no optimizations ~ 0.0104058 us
、-O3 ~ 0.00241034 us
;翻倍,而 Clazz::swap
方法保持一致。
为此,如果您希望 std::swap
方法调用您的自定义 Clazz::swap
方法,有几种方法可以实现此目的,因为 std::swap
方法不会自动调用任何 classes 定义的 swap
方法(即 std::swap
不调用 a.swap(b)
)。
您可以直接用您的专用 class 重载 std::swap
方法,例如:
namespace std {
void swap(Clazz& a, Clazz& b)
{
std::swap(reinterpret_cast<uint8_t&>(a), reinterpret_cast<uint8_t&>(b));
/* or a.swap(b) if your Clazz has private types that need to be
accounted for and you provide a public swap method */
}
}
但是,这样做意味着 argument-dependant lookup 找不到此专业化,因此调用 swap(a, b)
与 std::swap(a, b)
不同。
为了满足 ADL,您可以将 swap
方法包含在 Clazz
对象的同一命名空间中(无论是全局命名空间还是 named/anonymous 命名空间),示例:
using std::swap;
namespace ClazzNS {
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
};
void swap(Clazz& a, Clazz& b)
{
std::swap(reinterpret_cast<uint8_t&>(a), reinterpret_cast<uint8_t&>(b));
/* or a.swap(b) if your Clazz has private types that need to be
accounted for and you provide a public swap method */
}
}
int main()
{
ClazzNS::Clazz c1,c2;
c1.a = 1; c1.b = 61;
c2.a = 3; c2.b = 63;
swap(c1, c2); // calls ClazzNS::swap over std::swap
}
这只是为了让您可以调用 swap(a, b)
而不必明确命名空间。
如果你比较迂腐,你可以将它们混合在一起得到以下结果:
namespace ClazzNS {
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
void swap(ClazzNS::Clazz& other)
{
std::swap(reinterpret_cast<uint8_t&>(*this), reinterpret_cast<uint8_t&>(other));
}
};
void swap(ClazzNS::Clazz& a, ClazzNS::Clazz& b)
{
a.swap(b);
}
}
namespace std {
void swap(ClazzNS::Clazz& a, ClazzNS::Clazz& b)
{
ClazzNS::swap(a, b);
/*
you could also directly just call
std::swap(reinterpret_cast<uint8_t&>(a), reinterpret_cast<uint8_t&>(b))
or a.swap(b) if you wanted to avoid
multiple function calls */
}
}
int main()
{
ClazzNS::Clazz c1,c2;
c1.a = 1; c1.b = 61;
c2.a = 3; c2.b = 63;
c1.swap(c2); // calls ClazzNS::Clazz::swap
swap(c1, c2); // calls ClazzNS::swap
std::swap(c1, c2); // calls the overloaded std::swap
}
您的结果可能会有所不同,最终取决于您如何实施交换方法,但最好还是亲自测试一下;我希望这些数字能有所帮助。
C++测试代码:
#include <iostream>
#include <algorithm>
#include <string>
#include <ctime>
#include <csignal>
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
void swap(Clazz& other)
{
std::swap(reinterpret_cast<uint8_t&>(*this), reinterpret_cast<uint8_t&>(other));
}
};
static double elapsed_us(struct timespec init, struct timespec end)
{
return ((end.tv_sec - init.tv_sec) * 1000000) + (static_cast<double>((end.tv_nsec - init.tv_nsec)) / 1000);
}
static void printall(const Clazz& c1, const Clazz& c2)
{
std::cout << "c1.a:" << static_cast<unsigned int>(c1.a) << ", c1.b:" << static_cast<unsigned int>(c1.b) << std::endl;
std::cout << "c2.a:" << static_cast<unsigned int>(c2.a) << ", c2.b:" << static_cast<unsigned int>(c2.b) << std::endl;
}
int main() {
int max_cnt = 100000001;
struct timespec init, end;
Clazz c1, c2;
c1.a = 1; c1.b = 61;
c2.a = 3; c2.b = 63;
printall(c1, c2);
std::cout << "std::swap" << std::endl;
std::swap(c1, c2); // to show they actually swap
printall(c1, c2);
std::cout << "c1.swap(c2)" << std::endl;
c1.swap(c2); // to show again they actually swap
printall(c1, c2);
std::cout << "std::swap loop" << std::endl;
clock_gettime(CLOCK_MONOTONIC, &init);
for (int i = 0; i < max_cnt; ++i) {
std::swap(c1, c2);
}
clock_gettime(CLOCK_MONOTONIC, &end);
printall(c1, c2);
// rough estimate of timing / divide by iterations
std::cout << "std::swap avg. us = " << (elapsed_us(init, end) / max_cnt) << " us" << std::endl;
std::cout << "Clazz::swap loop" << std::endl;
clock_gettime(CLOCK_MONOTONIC, &init);
for (int i = 0; i < max_cnt; ++i) {
c1.swap(c2);
}
clock_gettime(CLOCK_MONOTONIC, &end);
printall(c1, c2);
// rough estimate of timing / divide by iterations
std::cout << "Clazz:swap avg. us = " << (elapsed_us(init, end) / max_cnt) << " us" << std::endl;
return 0;
}
希望能帮到你。
假设我有一个 C++ 结构:
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
};
我希望能够尽快交换此 class 的元素。只调用 std::swap(cl1, cl2)
还是专门化更好,如何?这行得通吗?
Clazz:swap(Clazz& other) {
std::swap(a, other.a);
std::swap(b, other.b); // how to make C++ swap the whole uint8_t value at once?
}
我希望足够的编译器在没有你提供交换的情况下正确地完成它,所以你应该衡量什么会更快但是你可能想尝试的一件事是转换为 uint8_t
void Clazz::swap(Clazz& other) {
std::swap(reinterpret_cast<uint8_t&>(*this), reinterpret_cast<uint8_t&>(other))
}
或者使用 memcpy
重写交换好的,现在去编译代码吧。已使用 -O2 优化级别的 gcc5.2 进行测试:
void test1(Clazz& a, Clazz& b) {
a.swap(b);
}
void test2(Clazz& a, Clazz& b) {
std::swap(a, b);
}
生成的代码:
test1(Clazz&, Clazz&):
movzbl (%rdi), %eax
movzbl (%rsi), %edx
movb %dl, (%rdi)
movb %al, (%rsi)
ret
test2(Clazz&, Clazz&):
movzbl (%rdi), %eax
movzbl (%rsi), %edx
movb %dl, (%rdi)
movb %al, (%rsi)
ret
假设您有以下代码:
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
};
Clazz c1,c2;
c1.a = 1; c1.b = 61;
c2.a = 3; c2.b = 63;
您可以在 class 中只执行 std::swap(c1, c2)
而无需额外的交换功能,事情就会按预期进行交换。请注意,结果可能会因实施和粗略的性能测试而异(
当下面的测试代码在没有优化的情况下在 g++ 4.2.1
中编译时,std::swap
循环的粗略平均值变为 0.00523261 us
而 Clazz:swap
粗略平均值变为至 0.00996584 us
。这意味着 std::swap
实现比 reinterpret_cast
将 Clazz
对象转换为 uint8_t
更快,但是,当您打开最大优化时(-O3
),时间更加明显,std::swap
循环进入 0.00102523 us
,Clazz::swap
循环进入 0.000739868 us
,速度显着提高 std::swap
方法。
此外,如果您的 Clazz
对象变得比默认构造函数更复杂,以至于您必须包含一个复制构造函数,那么 std::swap
的时间将几乎翻倍因为大多数实现将在创建临时对象时使用对象的复制构造函数。向 Clazz
对象添加一个空的默认构造函数和一个复制构造函数(即 Clazz(const Clazz& cp) : a(cp.a), b(cp.b) {}
)将 std::swap
次更改为以下内容:no optimizations ~ 0.0104058 us
、-O3 ~ 0.00241034 us
;翻倍,而 Clazz::swap
方法保持一致。
为此,如果您希望 std::swap
方法调用您的自定义 Clazz::swap
方法,有几种方法可以实现此目的,因为 std::swap
方法不会自动调用任何 classes 定义的 swap
方法(即 std::swap
不调用 a.swap(b)
)。
您可以直接用您的专用 class 重载 std::swap
方法,例如:
namespace std {
void swap(Clazz& a, Clazz& b)
{
std::swap(reinterpret_cast<uint8_t&>(a), reinterpret_cast<uint8_t&>(b));
/* or a.swap(b) if your Clazz has private types that need to be
accounted for and you provide a public swap method */
}
}
但是,这样做意味着 argument-dependant lookup 找不到此专业化,因此调用 swap(a, b)
与 std::swap(a, b)
不同。
为了满足 ADL,您可以将 swap
方法包含在 Clazz
对象的同一命名空间中(无论是全局命名空间还是 named/anonymous 命名空间),示例:
using std::swap;
namespace ClazzNS {
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
};
void swap(Clazz& a, Clazz& b)
{
std::swap(reinterpret_cast<uint8_t&>(a), reinterpret_cast<uint8_t&>(b));
/* or a.swap(b) if your Clazz has private types that need to be
accounted for and you provide a public swap method */
}
}
int main()
{
ClazzNS::Clazz c1,c2;
c1.a = 1; c1.b = 61;
c2.a = 3; c2.b = 63;
swap(c1, c2); // calls ClazzNS::swap over std::swap
}
这只是为了让您可以调用 swap(a, b)
而不必明确命名空间。
如果你比较迂腐,你可以将它们混合在一起得到以下结果:
namespace ClazzNS {
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
void swap(ClazzNS::Clazz& other)
{
std::swap(reinterpret_cast<uint8_t&>(*this), reinterpret_cast<uint8_t&>(other));
}
};
void swap(ClazzNS::Clazz& a, ClazzNS::Clazz& b)
{
a.swap(b);
}
}
namespace std {
void swap(ClazzNS::Clazz& a, ClazzNS::Clazz& b)
{
ClazzNS::swap(a, b);
/*
you could also directly just call
std::swap(reinterpret_cast<uint8_t&>(a), reinterpret_cast<uint8_t&>(b))
or a.swap(b) if you wanted to avoid
multiple function calls */
}
}
int main()
{
ClazzNS::Clazz c1,c2;
c1.a = 1; c1.b = 61;
c2.a = 3; c2.b = 63;
c1.swap(c2); // calls ClazzNS::Clazz::swap
swap(c1, c2); // calls ClazzNS::swap
std::swap(c1, c2); // calls the overloaded std::swap
}
您的结果可能会有所不同,最终取决于您如何实施交换方法,但最好还是亲自测试一下;我希望这些数字能有所帮助。
C++测试代码:
#include <iostream>
#include <algorithm>
#include <string>
#include <ctime>
#include <csignal>
struct Clazz {
uint8_t a : 2;
uint8_t b : 6;
void swap(Clazz& other)
{
std::swap(reinterpret_cast<uint8_t&>(*this), reinterpret_cast<uint8_t&>(other));
}
};
static double elapsed_us(struct timespec init, struct timespec end)
{
return ((end.tv_sec - init.tv_sec) * 1000000) + (static_cast<double>((end.tv_nsec - init.tv_nsec)) / 1000);
}
static void printall(const Clazz& c1, const Clazz& c2)
{
std::cout << "c1.a:" << static_cast<unsigned int>(c1.a) << ", c1.b:" << static_cast<unsigned int>(c1.b) << std::endl;
std::cout << "c2.a:" << static_cast<unsigned int>(c2.a) << ", c2.b:" << static_cast<unsigned int>(c2.b) << std::endl;
}
int main() {
int max_cnt = 100000001;
struct timespec init, end;
Clazz c1, c2;
c1.a = 1; c1.b = 61;
c2.a = 3; c2.b = 63;
printall(c1, c2);
std::cout << "std::swap" << std::endl;
std::swap(c1, c2); // to show they actually swap
printall(c1, c2);
std::cout << "c1.swap(c2)" << std::endl;
c1.swap(c2); // to show again they actually swap
printall(c1, c2);
std::cout << "std::swap loop" << std::endl;
clock_gettime(CLOCK_MONOTONIC, &init);
for (int i = 0; i < max_cnt; ++i) {
std::swap(c1, c2);
}
clock_gettime(CLOCK_MONOTONIC, &end);
printall(c1, c2);
// rough estimate of timing / divide by iterations
std::cout << "std::swap avg. us = " << (elapsed_us(init, end) / max_cnt) << " us" << std::endl;
std::cout << "Clazz::swap loop" << std::endl;
clock_gettime(CLOCK_MONOTONIC, &init);
for (int i = 0; i < max_cnt; ++i) {
c1.swap(c2);
}
clock_gettime(CLOCK_MONOTONIC, &end);
printall(c1, c2);
// rough estimate of timing / divide by iterations
std::cout << "Clazz:swap avg. us = " << (elapsed_us(init, end) / max_cnt) << " us" << std::endl;
return 0;
}
希望能帮到你。