Decorative image frame

喵耳朵

略懂算法 | 在学架构 | 梦想成为 | Top插画师

喵耳朵

CGO函数调用与数据转换

CGO是Go程序调用C库的一套机制,可以使Go语言能够站在C/C++的肩膀上。比如Go调用Tensorflow,也就是使用了CGO来实现的。很多语言都支持对C库的调用,一般称为FFI(Foreign Function Interface)。这里需要注意一下,一般的Python、Lua等语言调用C函数的时候,是通过函数签名找到函数的地址,然后直接调用对应的函数。而CGO会先生成中间文件,然后再一起编译调用。这里不具体展开了,总之只需要知道CGO比一般的FFI多了一层中间的步骤。在程序出错的时候,能理解调用栈的关系即可。参考 CGO 和 CGO 性能之谜

函数调用

package main

/*
CGO的标准写法:
1. 先用注释的方式写入,单行注释和多行注释都支持
  1.1 编译器环境变量
	1.1.1 CFLAGS: C编译选项
	1.1.2 CXXFLAGS: C++编译选项
	1.1.3 CPPFLAGS: C和C++共有的编译选项
	1.1.4 FFLAGS: Fortran编译选项
	1.1.4 LDFLAGS: 链接选项(不区分C和C++)
  1.2 C代码
2. import "C",相当于将所有的C函数放入虚拟的package C。之后通过`C.xxx`的方式来调用。需要紧跟注释之后。
*/

/*
#cgo LDFLAGS: -lm

#include <math.h>
double my_sqrt(double x) {
	return sqrt(x);
}
*/
import "C" // CGO的标准写法,相当于将C函数放入包`C`中
import "fmt"

func main() {
	a := 100
	fmt.Printf("sqrt(%v) = %v\n", a, C.my_sqrt(C.double(a))) // 调用上面自定义的函数
	fmt.Printf("sqrt(%v) = %v\n", a, C.sqrt(C.double(a)))    // 调用系统库函数(上述的m)
}

// Output:
//  sqrt(100) = 10
//  sqrt(100) = 10

通过上述的例子,我们可以看出,CGO可以调用C的库函数,也可以执行注释中的C代码的函数。一般注释中的C代码都是比较简短的。

接下来要介绍C和Go之间的数据是如何传递的。

Read More...

C++对象模型

本文主要是在看《Inside the C++ Object Model》的时候,想通过案例的方式加深一下理解。

首先,由于编译器有内存对齐的优化,比如:

#include <iostream>

class WithAlign {
    int a;
    char b;
};

#pragma pack(push, 1)
class WithoutAlign {
    int a;
    char b;
};
#pragma pack(pop)

int main() {
    std::cout << "WithAlign: " << sizeof(WithAlign) << std::endl;
    std::cout << "WithoutAlign: " << sizeof(WithoutAlign) << std::endl;
}

// Output:
//  WithAlign: 8
//  WithoutAlign: 5

内存对齐的类大小为8(按int 4字节对齐),未对齐的为5(int + char)。这里为了更容易理解,全部默认使用1字节对齐。

注意,本文中的内容均仅在自己的机器的Docker容器中做测试,环境为:64位 ubuntu 16.04, gcc 5.5。

# lsb_release -a
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.7 LTS
Release:        16.04
Codename:       xenial

# uname -a
Linux 06f25c7abffd 5.15.49-linuxkit #1 SMP Tue Sep 13 07:51:46 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

# gcc --version
gcc (Ubuntu 5.5.0-12ubuntu1~16.04) 5.5.0 20171010

何为C++对象模型?

引用《深度探索C++对象模型》这本书中的话:

有两个概念可以解释C++对象模型:

  1. 语言中直接支持面向对象程序设计的部分。包括构造函数、析构函数、多态、虚函数等。
  2. 对于各种支持的底层实现机制。这部分并没有标准化,因此不同的编译器可以有自己的实现。
Read More...

服务端基本概念和指标

这里主要介绍服务端架构工作中的一些常见的概念和指标,在我们部署、上线等运维工作时,方便排查问题,以及交流时的语义一致。

上游和下游 upstream and downstream

一般在谈论服务和调用关系的时候,我们会使用上游和下游来表示服务间的相关依赖。但是对于上下游的定义,会视情况而定的。

Stack Overflow上有个相同的问题:definition - Upstream / downstream terminology used backwards? (E.g. nginx) - Stack Overflow

Read More...

架构小试之IDL

为什么IDL的介绍也放在这里呢?一方面是我想不到放哪里,另一方面是之前说到,“架构”即“设计”,那么IDL、RPC框架也算是设计的一部分。不合理的选型在后续维护上会带来不小的麻烦。

本文主要介绍我用过的一些IDL,并结合真实案例,分析他们的优劣。

Read More...

我所理解的架构

本来是打算写一篇介绍我目前工作中有意思的设计的博客,写着写着发现设计的知识点还是有点多的,因此这里单独增加一栏,用来不断的更新相关的内容。

由于工作的变化,我从图像算法和框架,转向了服务端架构。做服务端架构,相较于算法和sdk框架,有着挺大的区别。而适应这一变化,确实需要花费挺长的时间,也学习到很多。

服务端的概念很多,很难在一本书上找到所有的答案,平时也只能自己多问多摸索。

这里总结了一下日常遇到的有意思的概念和技巧,希望耐心看完本文的你能够更快速的理解服务端的概念和开发过程。不至于在入职时每次讨论都一脸懵逼。

另外,我会在各部分的章节中,插入一些工作中遇到的真实案例。事实上,由于这些案例太过有意思,让我忍不住分享出来,所以才有了本文。(本文的目录也是根据这些案例归纳出来的。)

Read More...

Redis数据结构与对象

本文主要是《Redis设计与实现》的第一部分的总结,内容为数据结构和对象。这里类比了C++ STL中的数据结构,便于理解。

Redis是使用C语言编写的,C语言本身没有复杂的数据结构。因此Redis自己实现了一套底层的数据结构,这些数据结构作为工具被Redis的其他模块使用。

Read More...

关于多线程的三两事

多线程一直是编程中的重要的工具,它可以分充分的利用硬件资源,是我们用更少的时间去完成更多的事情。在之前的博客中,我有介绍了OpenMP的基本使用,OpenMP可以理解为多线程的一个合理和高效的一套抽象工具。这次,打算仔细的介绍多线程编程中的常见的概念和典型的案例。

典型的案例

说到多线程,最核心的问题就是保证数据的读写安全。为了达到此目的,我们需要多很多常见的数据结构做一些改造,从而适应多线程的场景。以下是我工作中比较常见到的一些使用场景:

  1. 线程池
  2. 读写锁
  3. 消息队列
  4. ConcurrentCache
  5. PingPang Buffer

在具体介绍这些使用场景之前,我们还是需要了解需要使用到的一些基本的工具:互斥量、条件变量、原子操作等。

Read More...