在Emacs中使用Org简单发送邮箱
配置方式
以outlook
邮箱为例
Server name:
smtp.office365.com
Port:587
Encryption method:STARTTLS
- 我使用的是
Spacemacs
,则在配置文件~/.spacemacs
中加入配置:
1 | (setq |
- 在
home
目录下创建.authinfo
文件,写入以下内容:
1 | machine smtp.office365.com login "email@hotmail.com" port 587 password "password" |
- GPG encryption
1 | $ gpg -c ~/.authinfo |
- Send email
在Org
日程管理界面中,通过快捷方式Leader m
打开发送窗口,在To:
后面输入对方邮箱地址并完善余下内容信息即可
其他
Manjaro
的pacman
安装wanderlust
1
$ sudo pacman -S --noconfirm --needed wanderlust
Leader f e d
配置Emacs
:
未完全配置:SMTP配置出错,跟上面配置冲突。只能IMAP收邮件到Mailbox
1 | ;; IMAP |
Org-Mode tutorial
- Reference from org-mode入门教程
Tutorial
1 | * org-mode 简介 |
Other
PostgreSQL查询优化
EXPLAIN语法
ANALYZE[boolean]
:执行statement,得到真是运行时间和统计信息VERBOSE[boolean]
:详细信息COSTS[boolean]
:cost值,默认打开BUFFERS[boolean]
:本次QUERY shared或local buffer的信息,包括命中未命中,脏写TIMING[boolean]
:输出时间开销FORMAT{TEXT|XML|JSON|YAML}
:输出格式
EXPLAIN代价因子校准
cpu_tuple_cost因子
- 创建测试表
1
create table tbl_cost_align(id int, info text, crt_time timestamp);
- 插入测试数据
1
2insert into tbl_cost_align select(random() * 2000000000)::int, md5(random()::text), clock_timestamp() frmo gnerate_series(1, 10000000);
insert into tbl_cost_align select(random() * 2000000000)::int, md5(random()::text), clock_timestamp() frmo gnerate_series(1, 1000000000); - 分析表,获得统计信息
1
analyze tbl_cost_align;
- 可以查看到占用的数据块个数
1
select relpages from pg_class where relname='tbl_cost_align';
- 执行checkpoint后关闭数据库,为了得到纯粹的物理磁盘的连续io请求cost常量,不能有shared buffer干扰
1
checkpoint;
1
$ pg_ctl stop -m fast
- 同时不能有OS Cache干扰,清除系统cachce
1
$ sync; echo 3 > /proc/sys/vm/drop_caches
- 使用systemtab来脚本分析
- 为了增加IO响应时间的柱状图输出,首先启动数据库,CPU亲和设置为1
1
2$ taskset -c 1 postgres >/dev/null 2&>1
$ psql
- 指定启动脚本
1
$ taskset -c 7 stap -e `...`
- 在psql执行explain analyze
1
explain (analyze, verbose, costs, buffers, timing) select * from tbl_cost_align;
- 通过数据求得
1
-- cpu_tuple_cost:total time - actual time = shared read * 块处理时间 + rows * x(xpu_tuple_cost)
random_page_cost因子校准 cpu_index_tuple_cst因子校准 cpu_operator_cost因子校准
- 为了保证公式准确性方面验证,把所有常亮设置为1
1
2
3
4set random_page_cost = 1;
set cpu_tuple_cost = 1;
set cpu_index_tuple_cost = 1;
set cpu_operator_cost = 1; - QUERY PLAN
1
2
3set enable_seqscan = off;
set enable_bitmapscan = off;
explain (analyze, verbose, costs, buffers, timing) select * from tbl_cost_align where id > 1998999963; - 计算公式为:
1
2
3
4-- = blocks * random_page_cost + cpu_tuple_cost * rows + cpu_index_tuple_cost * rows + cpu_operator_cost * ?
-- 其中cpu_index_tuple_cost和cpu_operator_cost的比例默认是2:1
-- 需要设置
set cpu_operator_cost = 2; - cao,略
auto_explain?not found
1 | -- 例如,以下设置将执行超过10ms的记录写到日志 |
- 如果需要配置到文件,则写入,然后重启:
1
2shared_reload_libraries = 'pg_stat_statements, auto_explain'
auto_explain.log_min_duration = 100ms
PostgreSQL插件及安装
PostgreSQL源码目录简介
contrib目录下的第三方插件
- adminpack:一些管理函数
- auth_delay:认证失败后延迟报异常,防止暴力破解
- auto_explain:将超过指定执行时间的SQL执行计划输出到日志
- btree_gin:gin索引方法的btree操作符扩展(某些情况下“多列gin组合索引”比“锁哥btree单列索引”的bitmap anding更高效
- chkpass:自动加密的字段类型(用UNIXcrypt()进行封装,仅支持前8位安全)
'abcdefghijkl'::chkpass = 'abcdefgh'
- citext:不区分大小写的数据类型
- cube:多维立方体类型,支持立方体对象的相同、相交和包含等关系
- dblink:PostgreSQL跨库操作插件
- dict_int:全文检索的字典模块,控制数字拆分最大长度,以确定数字在全文检索中的分词个数(maxlen = 6: 12345678被截断,rejectlong = true则忽略这个分词)
- dict_xsyn:全文检索的字典模块,设置分词同义词,支持同义词匹配
- dummy_seclabel:用于安全标签SQL测试
- earthdistance:cube、point类型计算地球两点间距离
- file_fdw:文件外部表模块
- fuzzystrmatch:单字节字符串之间的相似性判断
- hstore:存储kv数据类型,插件还提供了多个kv类型的函数和操作符,如数组、json、hstore间转换,kv存在判断,删除kv值
- intagg:int类型数组聚合函数
- intarray:int类型数组功能扩展
- isn:国际通用产品标识码数据类型,如ISBN、ISMN等
- lo:大对象可选木块,lo烈性和自动unlink大对象的trigger
- ltree:异构数据类型及操作函数操作符
- oid2name:oid转换成name的命令行工具,不属于extension
- pageinspect:用于读取数据库PAGE裸信息插件,可以读main、fsm、vm、FORK的页数据,用于debug
- passwordcheck:创建用户或修改用户密码时,检查密码安全性
- pg_archivecleanup:清除归档文件的命令,不属于extension
- pgbeanch:数据库性能测试,不属于extension
- pg_buffercache,输出当前shared buffer状态数据,细化到page number
- pgcrypto:服务端数据加密的扩展库
- pg_freespacemap:从行头信息的infomask获取行锁信息
- pg_standby:便于创建warm standby的命令行工具
- pg_stat_statements:跟踪数据库SQL,收集统计信息
- pgstattuple:行级统计信息,dead tuple、live tuples、table_len、free_space
- pg_tset_fsync:测试磁盘fsync速率
- pg_test_timing:测试系统定时器开销,开销越大,explain analyze时间结果越不准
- pg_trgm:将字符串拆分成3个一组的多个单元,用于测试两个字符串之间相似度,比分词暴力
- pg_upgrade:跨大版本的升级工具,如9.0->9.1
- pg_upgrade_support –pg_upgrade用到的服务端函数集
- pg_xlogdump:从xlog中dump出易读的底层信息
- postgresfdw:跨库的外部表插件
- seg:线段类型和浮点数的区间类型,及相关操作符,索引访问方法
- sepgsql:基于SELinux安全策略的访问控制模块
- spi:一些服务端的trigger函数
- sslinfo:输入ssl认证的客户端认证信息
- start-scripts:数据库启动脚本模块
- tablefunc:用于行列变换,异构数据处理
- tcn:提供异步消息输出的trigger
- test_parser:全文检索中的自定义parser的测试插件
- tserach2:全文检索相关插件,8.3以后不需要这个
- unaccent:全文检索插件
- uuid-ossp:生成UUID插件
- vacuumlo:大对象垃圾回收命令
- worker_spi:服务端worker编程返利
- xml2:xml相关插件
PostgreSQL数据库结构介绍
- base:对应pg_default(\db)表空间
- global:对应pg_global表空间,存放集群中共享对象例pg__database表,包含控制文件等
- pg_commit_ts:事务提交状态数据
- pg_hba.conf:数据库访问控制文件
- pg_log:数据库日志目录,根据配置定义,可能没有这个目录
- pg_multixact:共享行锁的事务状态数据
- pg_notify:异步消息相关的状态数据
- pg_serial:串行隔离级别的事务状态数据
- pg_snapshots:存储执行了事务snapshot导出的状态数据
- pg_stat_tmp:统计信息的临时文件
- pg_subtrans:子事务状态数据
- pg_tblspc:表空间的软链接目录
- pg_twophase:二阶事务的状态数据
- PG_VERSION:数据库版本
- postgresql.cofn:配置文件
- postgresql.auto.conf
- postmaster.opts:记录数据库启动时的命令行选项
- postmaster.pid:数据库启动的主进程信息文件,如$PGDATA目录,数据库启动时间、监听端口、IPC信息等
- pg_dynshmem
- pg_ident.conf
- pg_logical
- pg_replslot
- pg_stat
- pg_wal:应该就是存储WAL文件吧
- pg_xact
PostgreSQL第三方插件安装
- 通常的安装方法:
- 把第三方插件源码目录拷贝到contrib目录中
- 把pg_config加到PATH中
- make clean -j8 && make -j8 && make install -j8
- create externsion XXX;方式进行插件安装
Muduo技要2
性能指标
- 带宽MB/s,关心消息量,不管消息数
- 吞吐量QPS,TPS
- 延迟
- 资源使用率
test
sysctl -A | grep tcp.*mem
# 查看网络缓冲区sysctl -A | grep range
# 查看端口范围ntptime
# 查看ntp时间ntpq -pn
# ntp同步情况,service ntp start启动nc
# netcat $ nc ip port | [pipe]tcpdump
和Wiretshark
来性能分析tcpcopy
可以用来进行压力测试strace
prove
perf
# 查看程序的性能热点 e.g. perf record bin/memcached footprint ptmalloc 1000000 && perf reportnetstat -tpna | grep :prot
列出某服务客户端地址,netstat
或lsof
找出进程发起的连接nm,查看name mangilng
正确使用TCP
SO_REUSERADDR
- ignore
SIGPIPE
TCP_NODELAY
read
netcat
消息格式
Google Protocol Buffers
网络神话
Reactor和Proactor
- Reactor模式指“non-blocking IO + IO multiplexing”,Proactor模式是另一个。
- Recator的基本结构是事件循环(event loop)和事件驱动(event-driven)和事件回调实现业务逻辑,提高并发度和吞吐量,适合IO密集型横须
- CPU密集型程序应该使用线程池,即blocking_queue实现的任务队列
线程分配
- 当线程很廉价时,创造多于CPU数目的线程,一线程处理一个TCP连接,通常使用阻塞Blocking IO
- 当线程很宝贵时,创造跟CPU数目一样多的线程,一个线程处理多个TCP连接上的IO,即”non-blocking IO + IO multiplexing”
- 进程指fork的产物,线程指pthread_create的宝贵的原生NPTL Pthread线程,每个线程由clone产生,对应内核的task_struct
- 两种场合必须使用单线程:程序fork和限制程序的CPU占用率。此外,只有单线程能使用fork
多线程的性能优势
- 如果很少CPU负载让IO跑满,或很少IO负载就让CPU跑满,那么多线程没有性能优势
- 多线程适用场景:
- 多个CPU可用。
- 线程间共享数据,或者也可进程间共享
- 可修改的共享数据,或者也可进程间shared memory
- 提供非均质服务,如处理优先级事件
- lantency和throughput一样重要,即IO和CPU同样重要
- 异步操作
- scale up,能享受到多线程的优势
- 可预测性能。超过临界点会性能下降,线程数量不随负载变化
- 多线程划分责任和功能
C++编译链接模型精要
一些记录
- C++三大约束:C兼容、零开销(与Rust的零成本抽象区别?)原则和值语义
- 笼统地表示编译过程可分为:preprocessor/compiler/assembler/linker四个步骤,C++没有import或using(C++20可能出现),include头文件将导致当需要使用一个函数时预处理阶段必不可少的导入了一堆不相关的函数
- 一些编译选项:-Wall、-Wextra、-Werror、-Wconversion、-Wshadow
- 使用前向生命可以减少include,CCS规定不能重载&&、||、,三个操作符,Google规定不能重载一元
operator&()
,这样class就不能前向生命了 - 判断一个C++可执行文件是release还是debug模式可以通过看class template的短函数有没有被inline展开,在nm命令中查看可以发现,开启-O2编译的程序是看不到inline函数的任何信息的
1
2
3
4class Foo; // 前向生命
void bar(Foo &foo) {
Foo *p = &foo; // 取foo地址,重载了意思就变了
} - C++的链接模型相比C多了一些内容,name mangling和vague linkage,即一个符号多份互不冲突。C中不允许重复定义
一些名词
- 一次定义原则(ODR),C是的,C++不是的
C++设计
- POD和非POD
- 三/五原则:POD类型需要拷贝赋值非平凡类型时,就需要析构函数,而需要析构函数的类也需要拷贝和赋值操作(需要拷贝操作的类也需要赋值操作),否则使用编译器合成的将导致未定义行为或多重析构的危险
- 程序迁移工具:Porting Advisor,从X86(CISC)到arm kunpeng(RISC)
- 数据库测试工具:BenchmarkSQL安装ant -> 创建配置文件 -> 配置数据库连接 -> 场景配置 -> 数据准备 -> 执行测试
- 数据库连接:db、driver、conn、user/password
- 场景配置:warehouse仓库数量、loadWorkers数据并发数、Terminals并发用户数、runMins测试时间、runTxnsPerTerminal、limitTxns。。。
- 大数据测试工具套件:HiBench:评估框架速度、吞吐量和系统资源利用率
- 测试类别:micro、ml、sql、websearch、graph、streaming
C++并发相关错误类型
不必要的阻塞
- 死锁:指第一个线程等待第二个线程执行后才能继续,而第二个线程又在等待第一个线程,构成一个线程等待循环状态。
- 活锁:当第一个线程等待第二个线程,第二个线程又在等待第一个线程时,活锁类似死锁。活锁死锁区别在于等待过程不是阻塞状态而是不断循环检测状态,如spinlock。
- 在I/O或外部输入上的阻塞:当线程阻塞是因为等待某外部输入而无法继续执行,可能外部输入永远都不到来
竞争条件
- 数据竞争:指没有同步好对某个共享内存的并发访问
- 破坏不变量:表现为悬挂指针(线程删除了被访问的数据)、随机存储损坏(线程为局部更新造成读取数据不一致)或者双闲状态(重复释放?)
- 生存期问题:线程超时访问的某些数据已经被删除、销毁或访问的内存已经被另一个对象重用
需要思考的问题
- 哪些数据需要保护,防止并行访问?
- 如何保证数据是被保护的?
- 此时其他线程执行到代码的何处?
- 该线程用了哪些信号量?其他线程用了哪些信号量?
- 该线程各操作间有先后顺序要求吗?
- 线程载入的数据是否有效,是否被其他线程修改?
假如测试一个队列需要考虑的不同应用场景
- 一个线程在自身队列调用push()或pop()验证
- 空队列上一个线程调用push()另一个线程pop()
- 空队列和满队列上分别多个线程push()
- 空队列和慢队列上分别多线程pop()
- 空队列和满队列上分别多线程push()和一个线程pop()
- 空队列和满队列分别多线程push()和pop()
Rust跨语言
语言交互
- 外部函数接口(Foreign Function Interface,FFI)用于规范语言间调用的语法特征。其他有java的JNI(Java Native Interface),一样的意思
- 应用程序二进制接口(ABI)涵盖内容:
- 调用约定。参数、函数、返回值
- 内存布局。大小和对齐方式
- 处理器指令集。不同平台实现
- 目标文件和库的二进制形式。
- Rust中使用FFI需要通过extern和extern块对FFI接口进行标注,编译时会由LLVM默认生成C-ABI
链接与Crate Type
- 链接产生于程序开发的模块化。C和C++每个文件都是一个编译单元,Rust是以包(crate)为编译单元
- 链接过程就是编译器经过符号解析、存储空间分配和重定位的过程,最终生成可执行文件或库。
- 库分为静态库和动态库。静态库是普通目标文件集合的简单拼接,使用简单,但浪费空间;动态库则是把链接过程延迟到运行时,如运行时重定位,这比较省空间
- Rust支持四种库:Rust的dylib、rlib,和其他语言的cdylib、staticlib
- Rust库类型的命令行参数或crate_type属性:
--crate-type=bin
或#[crate_type = "bin"]
,表示是可执行文件,程序必须包含main函数--crate-type=lib
或#[crate_type = "lib"]
,表示生成Rust库,什么库编译器决定,一般是rlib静态库--crate-type=rlib
或#[crate_type = "rlib"]
,生成静态Rust库--crate-type=dylib
或#[crate_type = "dylib"]
,生成动态Rust库--crate-type=staitclib
或#[crate_type = "staticlib"]
,将生成静态系统库,Rust编译器永远不会链接它。用于和C语言链接--crate-type=cdylib
或#[crate_type = "cdylib"]
,生成动态系统库
交叉编译
- 本地平台上编译出需要在其他平台运行的程序叫做交叉编译。
- 可以使用rustc进行交叉编译,只需要传递target参数即可:如
$ rustc --target=arm-unknown-linux-gnueabihf hello.rs
- 也可以使用Cargo交叉编译,方法类似,在cargo
build命令传递–target参数即可。Rust默认使用cc作为交叉编译的链接器,修改配置文件可以指定链接器:1
2
3
4
5# $ car ~/.cargo/config
[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc-4.8" # 指定链接器
# 使用cargo交叉编译
# $ cargo build --target=arm-unknown-linux-gnueabihf - target triple格式是
{arch}-{vendor}-{sys}-{abi}
,分别代表编译程序的主机系统、供应商、操作系统、ABI接口,最后abi有时候可以省略
extern语法
- 有extern关键字和extern块。有三个extern API字符串是跨平台的:
extern "Rust"
、extern "C"
、extern "system"
- 另外有三个Rust编译器专用的ABI字符串:
extern "rust-intrinsic"
,代表Rust编译器内部函数的ABIextern "rust-call"
,Fn::call的ABIextern "platform-intrinsic"
,特定平台内在函数的ABI
与C/C++交互
Rust调用C函数
- 首先新建C/C++源代码文件夹,编写好代码后,使用
extern "C" {}
外暴露接口 - 在caogo.toml配置文件中,
[build-dependencies]
中使用cc库,然后在[package]
中将某个build.rs文件指定为自动化编译配置文件 - 在build.rs中引进包
extern crate cc
,使用cc.Build::new()
等方法初始化,在Rust源文件中正常使用时,用extern { ... }
声明指定的外部接口
C调用Rust库
- 首先在cargo.toml配置文件中
[lib]
设置Rust链接库名称,如:name = "callrust"
,将在Rust同级目录下写C头文件用于暴露Rust接口,并设置crate-type = ["staticlib", "cdylib"]
用于生成什么类型的链接库,库将产生在target/Debug下 - 编写Rust程序,将对应接口写在callrust.c中,并编写markdown文件:cargo用于编译Rust代码,gcc用于链接库并生成对应二进制文件
- 在另起的C源代码文件夹中直接
#include "callrust"
,然后调用接口函数即可
第三方工具
- rust-bindgen:根据头文件自动生成Rust FFI的C绑定,支持部分C++功能
- cbindgen:根据Rust代码自动生成头文件
- ctest:为Rust FFI的C绑定自动生成测试文件
- 移动平台下也有两个库:
- cargo-lipo:提供
cargo lipo
命令,自动生成用于iOS的通用库 - jni:提供Rust的JNI绑定,用于和Android平台交互
- cargo-lipo:提供
使用Rust提升动态语言性能
- 使用Rust为动态库编写扩展,能保证性能,还能提升内存安全
- 动态语言有自己的虚拟机,调用Rust代码不能像C/C++_直接链接Rust的链接库获取函数调用信息。因此动态语言提供FFI 基本都是基于libffi库来实现动态调用C函数的能力,兼容C-ABI的链接库都可以直接被动态调用。libffi库是动态语言虚拟机和二进制的一道桥梁
Rust与WebAssembly
WebAssembly是兴起的新的字节码格式,缩写是“WASM”,它是一个面向Web通用二进制和文本格式项目,它是为了作为C/C++/Rust语言的一种编译目标,在客户端提供一种接近本地运行速度的多语言编写代码的方式。某种意义上说,WebAssembly是一种中间语言(
IR),就像汇编(Assembly)语言那样是所有语言转换成机器码的通用底层语言,WebAssembly就是面向Web的汇编
- WebAssembly比JavaScript快的原因:
- WebAssembly体积更小,下载和解析更快。
- WebAssembly不受JavaScript约束,可以利用更多CPU特性。如64位整数、内存读写偏移量、非内存对齐读写和多种CPU指令等。
- 生成WebAssembly编译器工具链的优化和改进。如在Rust中,可以用wasm-gc工具优化生成的wasm文件大小。
- WebAssembly不需要垃圾回收。内存操作是手动控制,但也没必要担心内存泄漏,它的内存空间是JavaScript分配的对象,最终Javascript的GC管理
- WebAssembly在Web只是一个应用环境,WebAssembly还可以应用其他领域:桌面图形化程序、区块链智能合约和编写操作系统微内核
WebAssembly要点
- 模块。一个.wasm文件局势一个基本编译单位
- 线性内存。用于和JavaScript通信,是个可变大小的ArrayBuffer,由JavaScript分配
- 表格。用于存放函数引用,支持动态调用函数
- 实例。同一个模块的多个实例共享相同的内存和表格
- 栈式机器。WebAssembly指令的运行基于栈式机器定义,每种指令都是在站上进行出栈和入站操作
使用Rust开发WebAssembly
- 可以手写wat文本格式开发wasm模块,但效率不高,WebAssmebly设计之初是为了作为编译目标存在的,因此可以作为很多变成语言的编译目标:
- C/C++,可以通过EmScripten工具来编译到wasm。EmScripten是个LLVM后端工具,可以将LLVM中间码编译到asm.js。所以C/C++的编译流程可以通过任何一个LLVM前端工具生成LLVM IR,然后通过EmScripten生成asm.js,最后通过WebAssembly编译工具链Binaryen将asm.js生成wasm二进制格式。一些不支持wasm的浏览器也可以使用asm.js来代替
- Rust,支持wasm的两种编译目标:
- wasm32-unknown-unknown,使用LLVM WebAssembly Backend和lld链接器
- wasm32-unknown-emscripten,继续使用EmScripten,和C/C++类似
- 搭建wasm开发环境:
1
2$ rustup toolchain install nightly
$ rustup target add wasm32-unknwon-unknown --toolchain nightly - 生成wasm例子:
1
2
3
4$ cargo install wasm-gc
$ cargo build --target wasm32-unknown-unknown
$ cp target/wasm32-unknown-unknown-unknown/debug/hello_wasm.wasm output
$ wasm-gc output/hello_wasm.wasm output/small_hello.wasmWebAssembly开发生态
- Rust开发效率还是很低,WebAssembly标准值定义了四种类型:i32、i64、f32和f64。因此,Rust官方打造了一系列工具:
- wasm-bindgen:它的核心是促进JavaScript和Rust之间使用wasm进行通信,允许开发者直接使用Rust的结构体、JavaScript的类、字符串等类型
1
$ cargo +nightly install wasm-bindgen-cli
- wasm-pack:一站式构建、发布Rust编译的wasm到npm平台。不需要安装npm、node.js等JavaScript环境,wasm-pack会编译并优化生成JavaScript绑定,然后发布到npm中
1
2
3$ cargo install wasm-pack
# 在项目的根目录下执行命令,自动生成JavaScript相关文件,方便打包wasm到npm平台
$ wasm-pack build - cargo-generate:直接生成wasm-bindgen和wasm-pack项目模板,方便开发
1
2
3$ cargo install cargo-generate
# 命令生成模板会在Cargo.toml文件配置好wasm-bindgen,需要先安装wasm-bindgen-cli工具
$ cargo-generate --git https://github.com/your-responsbility
- wasm-bindgen:它的核心是促进JavaScript和Rust之间使用wasm进行通信,允许开发者直接使用Rust的结构体、JavaScript的类、字符串等类型
- 除此之外,社区的一些有代表性的框架:
- stdweb:Rust和WebAssembly实现的Web客户端标准库,可能会被web-sys替代
- cargo-web:方便编写Web客户端的Cargo子命令库
- yew:构建客户端Web应用的Rust框架,基于stdweb库
- percy:实现虚拟Dom,根据服务端HTML字符串渲染到浏览器的Dom
- ruukh:实验性Rust Web前端框架
扩展
UNIX格言
让每个程序就做好一件事。如果有新任务,就重新开始,不要往原程序中加入新功能而搞得复杂。
假定每个程序的输出都会成为另一个程序的输入,哪怕那个程序还是未知的。输出中不要有无关的信息干扰。避免使用严格的分栏格式和二进制格式输入。不要坚持 使用交互式输入。
尽可能早地将设计和编译的软件投入试用, 哪怕是操作系统也不例外,理想情况下, 应该是在几星期内。对拙劣的代码别犹豫,扔掉重写。
优先使用工具而不是拙劣的帮助来减轻编程任务的负担。工欲善其事,必先利其器。
- 原则1:你无法断定程序会在什么地方耗费运行时间。瓶颈经常出现在想不到的地方,所以别急于胡乱找个地方改代码,除非你已经证实那儿就是瓶颈所在
- 原则2:估量。在你没对代码进行估量,特别是没找到最耗时的那部分之前,别去优化速度
- 算法的常数复杂度很大。除非你确定n总是很大,否则不要用花哨算法(即使n很大,也优先考虑原则2)
- 原则4:花哨的算法比简单算法更容易出 bug、更难实现。尽量使用简单的算法配合简单的数据结构
- 原则5:数据压倒一切。如果已经选择了正确的数据结构并且把一切都组织得井井有条,正确的算法也就不言自明。编程的核心是数据结构,而不是算法
- 原则6:没有原则
《The Art of Unix Programming》
- 模块原则:使用简洁的接口拼合简单的部件。
- 清晰原则:清晰胜于机巧。
- 组合原则:设计时考虑拼接组合。
- 分离原则:策略同机制分离,接口同引擎分离。
- 简洁原则:设计要简洁,复杂度能低则低。
- 吝啬原则:除非确无它法,不要编写庞大的程序。
- 透明性原则:设计要可见,以便审查和调试。
- 健壮原则:健壮源于透明与简洁。
- 表示原则:把知识叠入数据以求逻辑质朴而健壮。
- 通俗原则:接口设计避免标新立异。
- 缄默原则:如果一个程序没什么好说的,就沉默。
- 补救原则:出现异常时,马上退出并给出足够错误信息。
- 经济原则:宁花机器一分,不花程序员一秒。
- 生成原则:避免手工hack,尽量编写程序去生成程序。
- 优化原则:雕琢前先要有原型,跑之前先学会走。
- 多样原则:决不相信所谓“不二法门”的断言。
- 扩展原则:设计着眼未来,未来总比预想来得快。
Rust错误处理
消除失败
- 常用六个Assert
- assert!:断言布尔表达式运行时一定返回true
- assert_eq!:断言两个表达式是否相等(使用PartialEq)
- assert_ne!:断言两个表达式是否不相等
- debug_assert!:等价于assert!,用于调试模式
- debug_assert_eq!
- debug_assert_ne!
分层处理错误
- Option
:处理有和无的情况,包含Some(T)和None两个选体。不存在的对应值应返回None,开发者需要对None进行处理,而不是直接引发恐慌 - Result<T, E>:用于处理合理解决的问题,如文件没找到、权限被拒绝、字符串解析出错等
- 线程恐慌(Panic):用于处理无法合理解决的问题
- 程序中止(Abort):使用abort函数将进程正常中止
unwrap系列方法
- unwrap方法可以取出包含于Some内部的值,但遇到None会引发线程恐慌
- unwrap_or方法是对match匹配包装的语法糖,该方法可以指定处理None时返回的值
- unwrap_or_else方法和unwrap_or类似,只不过参数是FnOnce()->T闭包
map系列方法
把Option
中值通过unwrap取出来再参与计算,会多出很多校验代码,显得冗余,map系列方法可以改善这种情况
- map方法是泛型方法,内部是match匹配,对于Some和None分别做了相应处理,参数为FnOnce(T)->U闭包,在无需取Option
的情况下在Option 内部计算,这种map叫组合子 - map_or方法
- map_orelse方法
- and_then组合子,直接返回值内容,而不经过Some包装
错误处理Result<T, E>
- 从语义上来说,Option
可以看做是忽略了错误类型的Result<T, ()>,Result<T, E>枚举体包含两个变体:Ok(T)和Err(E)。使用#[must_use]用来警告应该进行错误处理
恐慌(Panic)
- Rust中,使用恐慌安全(Panic Safety)来代替异常安全的说法。Rust中有一些代码会引发恐慌,如None进行unwrap操作、除以0等。Rust提供一个叫UnwindSafe的标记trait,专门标记恐慌安全你的类型,catch_unwind方法可以捕获恐慌,恢复当前线程,除了使用abort引发的进程中止无法捕获
- 可以使用std::panic::set_hook方法来自定义消息
扩展
Rust的一些概念
类型系统与多态性
- Rust一切皆表达式,表达式皆有值,值皆有类型,Rust中一切皆类型
- 现代编程语言包含三种多态形式:参数化多态、Ad-hoc多态和子类型多态:参数化多态和Ad-hoc是静多态,子类型多态是动多态
- 参数化多态指泛型,Ad-hoc多态叫特定多态:Haskell中是Typeclass,Rust中是trait;子类型多态出自面向对象语言
- Rust没有GC,内存由编译器分配,Rust代码被编译为LLVM IR,其中携带内存分配信息
- Rust也有少量的动态大小类型(DST),比如str,因为编译器不可能实现直到字符串,因此字符串切片&str是引用类型,它携带指针和长度信息
- Rust还支持零大小类型(ZST),比如单元类型和单元结构体,大小是0
- 如果ZST类型是空,那么底类型表示无,它是!,被称为Bang Type。
- ::<>是turbofish操作符。is_positive()可以判断正负
- Rust的泛型是静多态,编译器不管是泛型枚举、泛型函数还是泛型结构体,都会被单态化(Monomorphization)(静态分发)
- trait有4种用法:接口抽象、泛型约束、抽象类型和标签trait
- 孤儿原则(Orphan Rule):如果要实现某个trait,该trait和要实现该trait的类型至少有一个在当前crate中定义。
- Rust的trait限定与Java的泛型限定、Ruby和Python的Duck Typeing、Golang的Structural Typeing类似。trait是类型,是集合impl<T: A + B> C for T意思是为所有T属于A交B实现Trait C
- 泛型中使用trait限定,可以将类型的范围根据类型行为限定精确可控的范围。而OOP那套,可以使用trait object,它将在运行期根据虚表指针从虚表(析构函数、大小、对齐和方法等信息)找出正确指针,然后动态调用,这就是动态分发,区别于泛型静态分发
- 只有当trait的Self类型参数不能被限定为Sized且trait中所有方法都是对象安全时才能作为trait对象使用
- Sized、Unsize和?Sized的关系是,?Sized包含前者,Sized在编译期确定大小,而UnSize是不确定的
- 对象安全的方法必须满足三点之一
- 方法受Self: Sized约束
- 方法签名同时满足三点
- 不包含任何泛型参数。(否则trait对象在Vtable中将不确定调用哪个方法)
- 第一参数必须为Self类型或可以解引用为Self的类型。(如self、&self、&mut self和self: Box
- Self不能出现在除第一参数外的地方。
- trait中不包含关联常量,不过可以增加默认的关联常量,只需要使用const关键字
- Rust提供了5个重要标签trait,定义在std::marker,分别是Sized trait、Unsize trait、Copy trait、Send trait和Sync trait
- 动态大小类型不能随意使用,需遵循三条限制规则:
- 只允许通过fat pointer来操作Unsize,类型,如&[T]或&Trait
- 变量、参数和枚举变量不能使用DST
- 结构体最后一个字段可以使用DST,其他字段不行
- Copy trait继承自Clone trait,因此实现Copy功能同时需要实现Clone trait。Copy行为是隐式行为,开发者不能重载,它是简单的位复制,发生执行在变量绑定、函数参数传递和函数返回等场景中
- 任何类型都能实现Clone trait,但不是所有类型都能实现Copy trait,前提是所有成员都实现了Copy trait
- Rust可以通过实现Deref trait来自定义解引用操作,如果类型T实现了Deref<Target=U>,则该类型T引用或指针在引用时自动转换为U
- Rust的解引用有std::ops::Deref的.deref()、.as_ref()、std:borrow::Borrow的.borrow()、&*x和&x[..]
- From和Into是定义于std::convert模块中的两个trait,定义了from和into两个方法互为&str和String类型的反操作。标准库中还包含TryFrom和TryInto两种trait,它们是错误处理版本
- Rust标准库中包含了AsRef和AsMut两种trait,将值分别转换为不可变引用和可变引用
- Box
( pub struct Box<T: ?Sized>(Unique<T>);
)和Fn、FnMut、FnOnce、Sized等都加上#[fundamental]属性,代表享有特权,不必遵守孤儿规则 - 除了Rust,Rust还有重叠规则(Overlap),规则规定不能为重叠的类型实现同一个trait
- 迭代元素时,只能按值迭代,有时必须重新分配数据,不能通过引用来复用原始数据,为了支持引用类型,可以重用内部缓存区不需要重新分配内存,需要更高级的类型多态性,即泛型关联类型(Generic Associated Type, GAT)