研习录 - 操作系统

概念

  • 字大小(word size)
    • 在计算中,字是特定处理器设计使用的自然数据单位,是由处理器的指令集或硬件作为一个单元处理的固定大小的数据
    • 通常是计算机处理器在一个时钟内处理二进制数据的位数,是处理器的寻址能力和数据操作单位的大小

内存

内存对齐

现代处理器的内存子系统仅限以字大小(word size)的粒度和对齐的方式访问内存,内存对齐是指数据在内存中存放时,起始位置是某个数值的倍数,这个倍数通常是计算机的字大小。

内存对齐的目的是提高计算机系统访问数据的效率,减少访问内存的次数。当访问未对齐的内存数据且数据横跨两个计算机可以读取的字时,需要访问两次内存并做额外的 shift 操作才能读取到完整的数据。

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
31
32
33
34
35
#include <iostream>
using namespace std;

#pragma pack(push)
struct A {
char a;
double b;
};
#pragma pack(1)
struct B {
char a;
double b;
};
#pragma pack(4)
struct C {
char a;
double b;
};
#pragma pack(8)
struct D {
char a;
double b;
};
#pragma pack(16)
struct E{
char a;
double b;
};
#pragma pack(pop)

int main() {
cout << sizeof(char) << " " << sizeof(double) << std::endl;
cout << sizeof(A) << " " << sizeof(B) << " " << sizeof(C) << " " << sizeof(D) << " " << sizeof(E) << endl;
return 0;
}

输出如下。

1
2
1 8
16 9 12 16 16

内存屏障

写屏障(Store Barrier)读屏障(Load Barrier)全屏障(Full Barrier)

  • 防止指令之间的重排序
  • 保证数据的可见性

参考一

参考二

进程、线程、协程

操作系统由用户态切换至内核态的过程

  • 触发事件
    • 系统调用(new/delete、wait/sleep、创建线程等)、异常(InterruptedException)、外围设备中断
    • 处理器将系统调用号和参数放到寄存器,触发执行中断指令(现代操作系统每个线程对应一个内核调度实体,不会阻塞整个应用程序)
  • 异常/中断处理
    • CPU 暂停处理线程的正常处理,转而执行预定义的异常或中断处理程序
  • 保存上下文
    • 把寄存器状态和指令指针保存到内存
  • 切换到内核态
    • 在异常/中断处理程序中,处理器会将当前执行模式从用户态切换为内核态(修改处理器的特殊寄存器或标志位,使其处于内核特权级别)
  • 执行内核代码
    • 从寄存器取出系统调用号及参数,执行操作系统函数
  • 恢复上下文
    • 将应用程序的上下文从保存的位置恢复回处理器寄存器中
  • 切换到用户态
    • 处理器将执行模式从内核态切换回用户态,并从中断/异常处理程序返回到应用程序的正常执行位置

并发控制

  • 信号量
  • 锁(读写锁、互斥锁、自旋锁)
    • 自旋锁,盲等锁的机制,不放弃 CPU 使用权
    • 可重入锁,同一进程可对同一资源重复上锁
    • 乐观锁,假定每次读取写入的过程中不会有其他线程修改资源,每次读时不上锁,在写时判断是否被更改
    • 悲观锁,假定每次读取后,在写入时资源都会被其他线程修改,每次读时都上锁

CUDA

TODO

  • 内存模型
    • 寄存器
    • 共享内存
    • 常量内存
    • 全局内存
  • 缓存
  • 同步(block 内线程同步等)
  • 待整理
    • 内存占用(寄存器、共享内存)对并行性的影响
    • 设备信息(缓存、寄存器等信息)
    • SM 最大驻留线程束
    • SM 占有率

硬件

CPU & GPU

img

内存

内存分级。

img

Nvidia GPU 架构

层级

img

参考 GPU Performance Background User’s GuideNVIDIA Hopper Architecture In-Depth

simple-gpu-arch.svg

SM(Streaming Multiprocessor)

img

每个 SM 都有自己的指令调度器和各种指令执行管道。乘加是现代神经网络中最常见的运算,充当全连接层和卷积层的构建块,这两个层都可以被视为向量点积的集合。下表显示了 NVIDIA 最新 GPU 架构上各种数据类型的单个 SM 每个时钟的乘加运算。每个乘加都包含两个运算,因此可以将表中的吞吐量乘以 2,以获得每个时钟的 FLOP 计数。要获得 GPU 的 FLOPS 速率,需要将其乘以 SM 数量和 SM 时钟速率。例如,具有 108 个 SM 和 1.41 GHz 时钟频率的 A100 GPU 的峰值密集吞吐量为 156 TF32 TFLOPS 和 312 FP16 TFLOPS(应用程序实现的吞吐量取决于本文档中讨论的许多因素)。

multi-add-op.svg

核函数

定义

1
2
3
__global__ void function_name(...) {
...
}
  • __global__ 限定词
  • 返回值必须是 void

特性

  • 核函数在 GPU 上并行执行
  • 核函数只能访问 GPU 内存
  • 核函数不能使用变长参数
  • 核函数不能使用静态变量
  • 核函数不能使用函数指针
  • 核函数具有异步性
    • 使用 cudaDeviceSynchronize() 来做同步
  • 核函数不支持 iostream,打印需要使用 printf

核函数和线程等级

img

核(Kernel)是执行在 GPU 上的函数。应用程序的并行部分由K个不同的CUDA线程并行执行K次,而不是像常规的C/C++函数那样只执行一次。

每个CUDA内核都有一个__global__声明说明符。程序员通过使用内置变量为每个线程提供一个唯一的全局ID。

Figure 2. CUDA kernels are subdivided into blocks.

一组线程称为CUDA块(CUDA Block)。CUDA块被分组到一个网格(Grid)中。内核(Kernel)作为线程块网格(A Grid of Blocks of Threads)执行。

每个 CUDA 块被一个流式多处理器(Streaming Multiprocessor,SM)执行,不能被迁移到其他 SMs 处理(抢占、调试、CUDA动态并行期除外)。一个SM可以运行多个并发CUDA块,具体取决于CUDA块所需的资源。每个内核在一个设备上执行,CUDA支持一次在一个设备上运行多个内核。

img

上图展示了内核执行和GPU中可用硬件资源的映射。

限制。

  • CUDA架构限制每个块的线程数(每个块限制1024个线程)
  • 线程块的维度可以通过内置的block Dim变量在内核中访问
  • 块中的所有线程都可以使用内部函数__syncthreads 进行同步。使用 __syncthreads 块中的所有线程都必须等待
  • <<<…>>> 语法中指定的每个块的线程数和每个网格的块数可以是int或dim3类型。这些三尖括号标记从主机代码到设备代码的调用。它也称为 Kernel Launch

示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Kernel - Adding two matrices MatA and MatB
__global__ void MatAdd(float MatA[N][N], float MatB[N][N], float MatC[N][N])
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
int j = blockIdx.y * blockDim.y + threadIdx.y;
if (i < N && j < N)
MatC[i][j] = MatA[i][j] + MatB[i][j];
}

int main()
{
...
// Matrix addition kernel launch from host code
dim3 threadsPerBlock(16, 16);
dim3 numBlocks((N + threadsPerBlock.x -1) / threadsPerBlock.x, (N+threadsPerBlock.y -1) / threadsPerBlock.y);
MatAdd<<<numBlocks, threadsPerBlock>>>(MatA, MatB, MatC);
...
}

CUDA为线程和块定义了内置的3D变量。线程使用内置的3D变量threadIdx进行索引。三维索引提供了一种自然的方式来索引向量、矩阵和体积中的元素,并使CUDA编程更容易。类似地,块也使用称为block Idx的内置3D变量进行索引。示例的 CUDA 程序用于两个矩阵相加,显示了多维 blockIdx 和 threadIdx 以及其他变量(如 blockDim)。选择 2D 块是为了便于索引,每个块有 256 个线程,其中 x 和 y 方向各有 16 个线程。使用数据大小除以每个块的大小来计算块的总数。

调用

核函数调用需要指定线程模型。

1
kernel_function<<<grid, block>>>();

线程模型

img

  • 重要概念

    • grid,网格

    • block,线程块

  • 配置线程 <<<grid_size, block_size>>>

  • 最大允许线程块大小 1024

  • 最大允许网格大小 2^32 - 1

  • 每个线程在核函数中都有一个唯一标识

  • 内建变量(build-in variable)

    • 每个线程的唯一标识由 <<<grid_size, block_size>>> 确定,grid_size、block_size 保存在内建变量(build-in variable)
      • gridDim.x,该变量的值等于执行配置中变量 grid_size 的值
      • blockDim.x,该变量的值等于执行配置中变量 block_size 的值
    • 线程索引保存在内建变量
      • blockIdx.x,该变量保存一个线程块在一个网格中的索引,范围是 [0, gridDim.x)
      • threadIdx.x,该变量保存一个线程在线程块中的索引,范围是[0, blockDim.x)
  • 多维线程

    • CUDA 可以组织三维的网格和线程块
    • blockIdx 和 threadIdx 是类型为 uint3 的变量,该类型是结构体,有 x、y、z 三个变量
    • gridDim 和 blockDim 是类型为 dim3 的变量,该类型是结构体,有 x、y、z 三个变量
    • 多维网格和多维线程块本质上是一维的,GPU 物理上不分块
    • 数量限制
      • 网格大小
        • gridDim.x,[1, 2^31)
        • gridDim.y,[1, 2^16)
        • gridDim.z,[1, 2^16)
      • 线程块
        • blockDim.x,1024
        • blockDim.y,1024
        • blockDim.z,64
    • 定义
1
2
dim3 grid_size(g_x, g_y, g_z);
dim3 block_size(g_x, g_y, g_z);

img

nvcc 编译流程

  • nvcc 分离源代码为
    • 主机(Host,__host__)代码
    • 设备(Device,__global__)代码
  • 主机代码是 C/C++ 语法,设备代码是 C/C++ 扩展语言
  • nvcc 先将设备代码编译为 PTX(Parallel Thread Execution)伪汇编语言,再将 PTX 代码编译为二进制的 cubin 目标代码
    • 编译 PTX 时,需要指定 -arch=compute_XY 选项,指定虚拟架构的计算能力,用于确定代码中能够使用的 CUDA 功能
    • 编译 cubin 时,需要指定 -code=sm_ZW 选项,指定一个真实架构的计算能力,用以确定可执行文件能够使用的 GPU

img

  • PTX
    • PTX 是 CUDA 平台为基于 GPU 的通用计算而定义的虚拟机和指令集
    • nvcc 编译命令总是使用两个体系结构
      • 一个是虚拟的中间体系结构
      • 另一个是实际的 GPU 体系结构
    • 虚拟架构更像是对应用所需的 GPU 功能的声明
    • 兼容性
      • 虚拟架构应该尽可能选择低版本,适配更多实际 GPU
      • 实际架构应该尽可能选择高版本,充分发挥 GPU 性能

参考文档

运行

步骤

  • 设置 GPU 设备
    • 使用 cudaGetDeviceCount 可以获取 GPU 设备数量
    • 使用 cudaSetDevice 来设置使用的设备
  • 分配主机和设备内存
  • 初始化主机中的数据
  • 从主机复制数据到设备
  • 调用核函数在设备中运行
  • 将计算得到的数据从设备传给主机
  • 释放主机与设备内存

内存

内存模型

image-20240817210959432

基于 <<<grid, block>>> 的 cuda 程序内存模型。内存可见性及生命周期如下。

image-20240817211312716

内存管理

  • 内存分配,cudaMalloc,对应标准库 malloc
    • 运行环境:设备、主机
  • 数据传递,cudaMemcpy,对应标准库 memcpy
    • 支持主机->主机、主机->设备、设备->设备、设备->主机 的内存拷贝,支持默认选项自动识别
    • 运行环境:主机
  • 内存初始化,memset,对应标准库 cudaMemset
    • 运行环境:主机
  • 内存释放,cudaFree,对应标准库 free
    • 运行环境:主机、设备

函数

  • 设备函数(__device__ 修饰)
    • 只能运行在 GPU 设备上
    • 设备函数只能被核函数和其他设备函数调用
  • 核函数(__global__ 修饰)
    • 一般由主机调用,在设备中执行
    • __global__ 修饰符不能和 __host____device__ 同时用
  • 主机函数(__host__ 修饰或无修饰)
    • 主机端 C++ 函数

说明

  • 可以使用 __host____device__ 同时修饰一个函数来减少冗余,编译器会针对主机和设备分别编译

参考

CUDA - 版本及环境搭建

CUDA (Compute Unified Device Architecture)是英伟达开发的并行计算平台,为使用 GPU 加速的程序提供开发环境(当前仅针对英伟达设备)。

API

CUDA 提供两层 API,驱动(Driver) API 及运行时(Runtime) API。Runtime API 是基于 Driver API 编写的、使用更为简便的 API。

Compute Capability

英伟达每款 GPU 会有一个叫做 Compute Capability 的版本号,用于标识设备的计算平台兼容性。其直译 计算能力 略有歧义,并非代表设备的计算性能。

rapidjson - usage

加载

从字符串加载

1
2
3
4
5
6
7
8
9
10
11
#include "rapidjson/document.h"

// 加载 Object
rapidjson::Document doc;
doc.SetObject();
doc.Parse(data.c_str()); // data: std::string

// 加载 Array
rapidjson::Document arr;
arr.SetArray();
arr.Parse(data.c_str()); // data: std::string

Type

1
2
3
4
5
6
7
kNullType = 0,      //!< null
kFalseType = 1, //!< false
kTrueType = 2, //!< true
kObjectType = 3, //!< object
kArrayType = 4, //!< array
kStringType = 5, //!< string
kNumberType = 6 //!< number

Object

1
2
3
4
5
// 查找元素
auto it = doc.FindMember("field");
if (it == doc.MemberEnd()) ... // 判断是否存在
// 遍历元素
for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) ...

Array

1
2
3
4
5
6
7
8
9
10
// 判断是否为空
arr.Empty();

// 遍历
for (auto it = arr.Begin(); it != arr.End(); ++it)

// 取第一个
auto e = arr.Begin();
e.GetObject(); // 作为 Object, e.GetObject().FindMember("field") 查找元素
e.GetString(); // 作为 String

Value

创建

1
2
3
4
5
6
7
8
9
// Object Value
rapidjson::Value v(rapidjson::kObjectType);
// Array Value
rapidjson::Value v(rapidjson::kArrayType);
// String Value
rapidjson::Value v("string_value");
// Number Value
rapidjson::Value v(1);
rapidjson::Value v(1.1);

c++ style guide

风格指南

Google C++ Style Guide

注释文档化

工程中使用统一代码风格

.clang-format

安装工具

1
brew install clang-format

导出指定格式的 .clang-format 配置文件

1
2
clang-format -style=Google -dump-config > .clang-format
clang-format -style=llvm -dump-config > .clang-format

格式化文件

1
2
3
4
5
6
7
8
$ cat t.cpp
#include "iostream"
int main() {
return 0;
}
$ clang-format -style=Google t.cpp
#include "iostream"
int main() { return 0; }

Inplace 格式化文件

1
$ clang-format -style=Google -i t.cpp

在 IDE 中使用 .clang-format

  • CLion 可以识别并应用项目根目录下的 .clang-format 文件,如果没有的话,可以点击右下角的 spaces 信息框,再点击 Enable ClangFormat

image-20231110182136072

跳过格式化

1
2
3
// clang-foramt off
{codes}
// clang-foramt on

Clangformat 配置示例

1
2
3
4
5
6
7
8
---
# use defaults from the LLVM style
BasedOnStyle: LLVM
ColumnLimit: 120
---
Language: Proto
# Don't format .proto files.
DisableFormat: true

c++ 各标准版本特性

版本说明

WG21(The ISO C++ committee)有严格的时间限制,每 3 年推出一版新的标准。最新的版本信息参考 WG21 官网。每个正式标准确定前,会使用草案名(draft),比如 c++1zc++2a

各版本特性

C++ 20(C++2a)

语言特性

库特性

C++ 17(C++1z)

语言特性

库特性

C++ 14(C++1y)

语言特性

库特性

C++ 11(C++0x / C++1x)

语言特性

库特性

参考

小狗钱钱

当利息降到零,我们的投资回报甚至会更丰厚。你要知道:唯一不变的就是变化。

前言

有了足够的金钱,我们能更有尊严地生活,更好地对待自己和他人。

个人化的经验很难效仿,但最基本的真理却能被普遍运用。

追问我们自己到底想要什么样的人生,这需要勇气。

不是因为困难重重,所以我们心生畏惧,而是因为我们心生畏惧,所以事情变得困难重重。

第一章 一只白色的拉布拉多犬

我也认为金钱不是生命中最重要的东西。可如果时时处处都缺钱,那么钱就变得举足轻重了。

只有你自己真正渴望学习,我才能帮得了你。

第二章 梦想存储罐和梦想相册

不是试一试,而是去行动。所谓尝试,只不过是在为失败提前找借口、为自己找退路。

在行动之前,不要评判。不去想象成功的美好,就不能达成目标。精力集中在哪,哪就会开花结果。只不过,大部分人总是把功夫花在并不喜欢的事情上,而不去想自己真正渴望拥有的东西。

注意到了吗?你往往先找各种理由来证明一件事做不成。

钱的多少并不是最关键的,更重要的是,我们拿钱来做什么。

在你展翅飞翔之前,你就必须相信自己一定能到达目的地。你想象得越多,你的愿望就会变得越发强烈。然后,你就会开始寻找机会去实现愿望。吉雅,机会到处都是,但只有去寻找,才能发现。只有拥有强烈的渴望,才会去寻找。而只有不断地去想象,才会拥有强烈的渴望。

第三章 能挣很多钱的男孩达瑞

你都没试过,却总是先想着行不通,这样肯定不会成功啊。

是否自信决定着你是否敢去做某些事情,如果没有这份自信,你就不会开始去做。而如果不动手去做,就什么也不会发生。

要始终去帮助别人解决问题,才能挣到钱。要把精力始终集中在你知道的、能做到的和拥有的东西上。

第四章 堂哥的致富经

不要总是想那些做不成的事情嘛,你得多想想什么能做成。

不能光指望着一份工作,因为它可能比你预想中更快结束,你得抓紧时间接着去寻找下一个工作机会。

第五章 钱钱以前的主人

这正是很多不富裕的人会犯的错误。他们总是有很多十万火急的事情要做,却没有时间考虑真正重要的问题。

第一,即使遇到了困难和问题,也得实施你的计划。如果事情一切顺利,谁都能完成任务,只有在出现真正的问题时,谁强谁弱才能见分晓。只有少部分人能继续坚定不移地贯彻执行他们的计划。而那些特别富有的人,更是擅长在最困难的时候做出最漂亮的成绩。第二,当一切进展顺利时,你也应当坚持做下去。

苦难和问题总是层出不穷,尽管如此,你仍然要每天坚持下去,坚持去做对你的未来意义重大的事情。它们最多花掉你十分钟的时间,能给你带来真正的改变。大部分人总是日复一日地停留在原地。他们总是期待周围环境会为自己发生改变,却忘记了首先应该改变的就是他们自己。

总有千百种事情可能分散你的注意力。

72小时法则。当你决定做一件事情时,必须在 72 小时之内开始行动,否则就很有可能再也不会做了。

第六章 债务-爸爸妈妈犯过的错误

大部分人都认为工作是很艰苦的,是一种负担。其实,只有做自己真正喜欢的事情,才可能真正获得成功。

在我看来,所有的消费贷款都是不明智的。聪明的人只是把以前积攒的财富用于支出。

(针对债务危机)把扣除生活费用后所剩的一半存下来,剩下的一半用于支付消费贷款。最好不要去申请消费贷款。每个月应该尽可能少地偿还贷款。

第七章 拜访金先生

金先生不是个寻常人,做事也总是不同寻常。他不在会别人做什么,他只做自己认为正确的事情。

我也有自己的秘密,所以和我说话的人当然也可以有自己的秘密。

疯狂的目标也不见得就比普通的、微小的目标更难实现。如果你树立了远大的目标,那毫无疑问,你就必须为此付出极大的努力。

为什么你做了喜欢的事情,就不应当获得金钱上的回报呢?正因为你真心付出了,你的工作才有价值。

我自己的习惯是,不论挣到多少钱,都为我的鹅存下收入的 50%,为梦想储蓄罐存下 40%,剩下的 10% 用于开销。

我越是关注自己身上的伤痛,疼痛就会越剧烈。谈论伤痛,就像在伤口上再撒上一把盐。所以,从很多年以前,我就不再抱怨了。

第八章 特伦夫太太的邀请

用最少的还款额度偿还贷款,才是最明智的做法。

第九章 冒险开始了

等待是世界上最愚蠢的事情。

第十一章 爸爸妈妈不明白的事情

仔细观察,幸运只不过是充分准备加上努力工作的结果。

勇敢不是毫不畏惧。勇敢的意思是,一个人尽管心怀恐惧,但仍能克服恐惧向前走

工作本身往往最多只值报酬的一半,另一半价值来源于你的想法和实施这个想法的勇气。

爸爸是个好人,可惜他有个坏毛病,就是总将自己的境况归咎于别人和外部环境,仿佛只有他自己是个牺牲品,而别人都是幸运儿。

笨人只有一次好运,聪明人永远都有好运。

你们总是想着怎么才能暂时应付过去,而更聪明的做法是要找到长期又有效的解决办法啊。

第十二章 特伦夫太太归来

金钱只会留在那些为之做好了准备的人身边,用非法手段得到不义之财的人,反而会过得比没钱时更糟糕。

想要过得幸福而充实,就得先改变自己,钱可不能为他们代劳。

金钱会暴露一个人的品行。金钱就像放大镜一样,它会让你把自己看得更清楚。

以前,我喜欢首先关注自己做不成的事情,现在回更专注于我能做成的事情。这样一来,我就能更多地去寻找解决问题的办法,而不是寻找逃避问题的借口。

也许金钱不是生活中最重要的东西,但如果处处缺钱的话,它就变得无比重要了,生活会为钱所累,人会变得很不正常,会跟身边的人吵架,会觉得挫败沮丧,觉得自己一无是处。

没有人能强迫你做自己不愿做的事情,只有你自己能强迫自己去做。

我生命中最美好的事情之所以发生,都是因为我做了原本不敢做的事情。

最珍贵的礼物都是自己送给自己的。一旦克服了丢面子的恐惧,世界就会对你敞开大门。

能够阻挡你做自己喜欢的事情的,只有恐惧。但是,战胜了恐惧后,你就会成长。

第十三章 严峻的危机

你可以成为这样一个人,有能力帮助别人,也能够让别人信任你,愿意向你求助。

成功会使人骄傲,如果你骄傲自大,就会停止学习,不学习,人就不会成长和进步。

不能在困难面前退缩。对苦难、错误和羞耻的恐惧,毁掉了无数人的生活。

我们的恐惧总是源于对那些不确定的事情的想象,我们越是设想失败的可能性,就会越害怕。一旦你把精力集中在积极的目标上,就不会心生畏惧了。

第十四章 投资俱乐部

我们一致认为,只要学会我们的咒语,就能像变魔法一样从无到有地变出钱来:热爱并渴望金钱;自信,有想法,敢于做自己喜欢的事情;把钱分成三部分,分别用于日常开销、梦想目标和养鹅账户;之后,进行明智的投资;以及享受生活。

投资需要注意三点:应当把钱投资在安全的地方;我们的钱应该生出很多金蛋;我们的投资必须简单易懂。

决定一件东西的价值的唯一要素是,人们愿意支付多少钱来购买它。

不要急着投资,在投资之前,必须先弄清楚自己究竟在做什么。

第十五章 演讲

如果你没有强迫自己,你就永远都不知道自己的能力究竟怎么样。别忘了,最让我们感到骄傲的事情,往往就是那些最难做到的事情。

第十六章 投资俱乐部在行动

一旦计划投资基金,就说明我们打算将一笔钱放在里面至少 10 年。对那些能等待这么长时间的人来说,基金几乎是一种零风险的投资。

筛选优质基金的注意事项:基金应当至少有 10 年的历史;应当是大型的跨国股票基金;找到过去最近 10 年里收益表现最好的基金。

第十七章 爷爷奶奶害怕风险

只有当我们把它卖出的时候,才会有亏损。

当我第一次遇到行情大跌时,反应和你们一模一样。经历过好几次这种所谓的危机,一两年后,市场总会恢复的。每次都是如此。

存折就是个金钱粉碎机。

怎么可以这样随便就下结论呢?下结论之前,至少也看一看我们的投资是怎么回事啊。你们不能因为自己不熟悉,就说这事儿危险哪。

你应该把一部分钱放在绝对无风险的投资中。也得留着备用的资金,这样才能达到分散风险的最佳效果。

第十八章 大冒险的终结

以前他总是怀疑自己能不能独当一面,现在他却知道,只要将自己不喜欢也不擅长的事情交给其他人就行了。

挣来的钱一分为三,其中的 50% 用于养鹅,40% 用于实现我的中短期目标,10% 用于零花。

最重要的是,他始终把钱视为一种再正常和自然不过的事物,在他的影响下,我对金钱的态度也发生了翻天覆地的变化。

重要的是,你是否能够听到并理解我说的话。就像你现在正在写的那本书一样,有些人读到了它,却可能根本听不见它传达的意思,也不会做出任何改变,而另一些人读过后开始学习如何更聪明地和金钱打交道,从而拥有更幸福、更富有的人生。

不要为已失去的东西悲叹,而要对你曾经有由它的时光心存感激。

后记

墙一旦倒塌,视野便会更开阔。

读书 - 信息流广告入门

自从开始工作,一致在做推荐相关的事情,做程序化广告交易平台之后,买了《信息流广告入门》这本书。读完有一段时间了,再简单说下,有必要的话再补充。

这本书从产品、投放角度来阐述信息流广告相关的事情,没有聊技术。多是一些广告相关的概念和基础,投放相关的知识。

对于想从事广告投放相关的工作,可以作为入门书籍来读。对于从事广告交易平台相关的技术工作,也可以读下,从产品和投放视角了解广告,里面的一些基础概念,对理解业务还是有帮助的。

整体来说,内容还是比较浅显易懂,适合入门。

操作系统 - 协程

我们常说的协程是在用户态实现的一种编程范式,严格意义上来说,协程并不属于操作系统的范畴。本质上来说,协程是被协程调度器处理的一段内存数据,他包含协程本身的 Context 信息及我们任务代码的地址信息。

协程和线程的区别

直观上,可以把协程和线程做对比。都有任务调度机制,不过线程是内核级别(抢占式),而协程是用户级别(协作式),所以基于协程的任务切换开销要由于线程。都可以被执行、挂起、恢复,在 IO 密集型的任务中,通常伴随大量的挂起和恢复操作,如果使用协程来实现,可以节省大量内核/用户态切换、线程上下文切换开销。

但是需要注意的是,线程是完全并行的,而协程则不一定,要看协程调度器的实现是如何和线程绑定的,可以做到并发,但不一定能做到并行。对于计算密集型行任务,要充分考虑使用协程是否符合预期。

协程切换同样需要保存协程的运行时上下文信息(栈和寄存器信息)。

协程和函数的区别

  • 协程有自己的上下文(状态、运行指令位置、寄存器信息等)
  • 协程可以被打断,可以主动暂停
  • 协程具备更好的并行、协作能力
  • 协程可以实现 lazy 计算

协程切换过程

参考