Home avatar

This is my blog

QT时间转换接口的加锁问题

遇到问题的场景

  • 负责旧司的后台工具, 要转换大量的 K 线文件 (一共百 GB 级别)。用到多线程转换。但是在转换过程中, CPU 占用和磁盘速度没有拉满。
  • 因为工具是在 Windows 平台运行的, 所以使用了 进程资源管理器 查看进程的运行情况。发现内核时间占用比较多。所以猜测是加锁导致的。

解决问题的思路

  • 直接使用控制变量法是最快的。因为线程池的 worker 使用最频繁的代码块是转换 K 线的各个字段的 for 代码。
  • 注释该 for 的代码。然后运行, 发现 CPU 占用和磁盘直接拉满。
  • 基本确定是该块代码加锁了。
  • 这时发现时间转换使用了 QT 的 QDateTime::fromString。我不知内部实现, 所以注释日期转换代码。发现 CPU 占用和磁盘速度也是拉满的。
  • 所以基本确定是 QDateTime::fromString 代码加锁了。
  • 最后自己根据时间字符串的分隔符取出各个时间字段 (年、月、日、时、分、秒)。

查看 QDateTime::fromString 的实现

为了满足自己的好奇心去查看了 QT 的 QDateTime::fromString 的实现:

记录一次向 AUR 提交包

配置 ssh 的公私钥

生成公私钥:

1
ssh-keygen -f ~/.ssh/aur    # 默认是 ed25519

~/.ssh/config:

1
2
3
Host aur.archlinux.org
  IdentityFile ~/.ssh/aur
  User aur

向 aur 账号添加公钥:

1
My Account -> SSH 公钥 -> 更新

创建软件包仓库并提交

1
2
# 如果仓库不存在, 则会自动创建此仓库
git clone ssh://aur@aur.archlinux.org/<your_pkg_name>.git

编写 PKGBUILD (叫 AI 帮写一个, 再修改即可)。注意: arch 不要使用 ‘any’, 有如下架构: arch=('i686' 'pentium4' 'x86_64' 'arm' 'armv7h' 'armv6h' 'aarch64')。See PKGBUILD ref

Enable_if 的用法

函数返回值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
foo(T t) {
    std::cout << "Integer: " << t << std::endl;
    return t;
}

template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
foo(T t) {
    std::cout << "Floating point: " << t << std::endl;
    return t;
}

int main() {
    foo(10);
    foo(3.14);
    return 0;
}

模板的默认类型实参

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
#include <type_traits>

// 如果我只想该函数模板只接收整型
template <typename T, typename /*U (可以不写)*/ = std::enable_if_t<std::is_integral<T>::value, bool>>
void foo(T t) {     // 只有需要一个版本的 foo 的情况下。因为模板的默认实参不同的两个模板是相同的东西。
                    // 如果想要两个版本以上, 则将 enable_if 做成模板类型, 请继续向下看。
  std::cout << "Integer: " << t << std::endl;
}

int main() {
  foo(10);
  foo(10.1);    // error: no matching function for call to ‘foo(double)’
  return 0;
}

模板类型形参

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <type_traits>

/* 如果需要两个以上版本的 foo 的情况下。*/

template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> /* param (形参名也可以省略) */ = true>
void foo(T t) {     
  std::cout << "Integer: " << t << std::endl;
}

template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
void foo(T t) {     
  std::cout << "Float: " << t << std::endl;
}

int main() {
  foo(10);
  foo(10.1);
  return 0;
}

模板的特化形参

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <type_traits>

template<typename T, typename U>
class Foo {};

template<typename T>
class Foo<T, std::enable_if_t<std::is_same_v<T, int>, T>> {
  public:
    Foo() {
      std::cout << "int" << std::endl;
    }
};

template<typename T>
class Foo<T, std::enable_if_t<std::is_same_v<T, float>, T>> {
  public:
    Foo() {
      std::cout << "float" << std::endl;
    }
};

template<typename T>
using MyFoo = Foo<T, T>;

int main() {
  MyFoo<int> foo1;
  MyFoo<float> foo2;
  return 0;
}

扩展 enable_if

IsIntegral:

Cpp 初始化风格

cpp 的初始化风格

兼容 C 的初始化风格:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int i = 10;
int a[] = {1, 2, 3};

struct Foo {
  char c;
  int i;
};

union Bar {
  char c;
  int i;
};

Foo foo = {.c = 'A', .i = 42};
Bar bar = {.c = 'A'};

但是 Cpp 引入了类, 这样会导致一个问题:

内存模型和原子操作的总结

内存模型和原子操作的设计目的

通过底层的设计可以让操作系统或 cpu 给用户提供一种操控颗粒度更小的各个线程之间的内存访问同步的功能。

原子操作和互斥量的应用场景

如果仅需要对少量的简单数据进行原子操作,原子锁可能比互斥量更高效。但在涉及到复杂数据结构或需要独占资源访问的情况下,互斥量提供了更强的同步能力,但通常会涉及线程的切换和上下文切换,因此可能会引入一定的性能开销。

0%