八月初开始接触《深入理解计算机系统》这本书,当时看的是英文版,花了一个月的时间大体上过了一遍,但我知道其实还是有很多没看懂的地方,于是借了一本中文版,如今总算可以说我真正看过了这本书,可以写一写读书笔记了。
深入理解计算机系统读书笔记
p39-40
把二进制数转为无符号十进制数 B2U(w)=∑xi*2^i (0<=i<=w-1)
B2U(4)([1011])=1*2^3+0*2^2+1*2^1+1*2^0=11
转换为补码形式 B2T(w)=-x(w-1)*2^(w-1)+∑xi*2^i (0<=i<=w-2)
B2T(4)([1011])=-1*2^3+0*2^2+1*2^1+1*2^0=-5
p45-46
无符号与补码之间转换
T2U(w)=x+2^w (x<0)
=x (x>=0)
U2T(w)=u (u<2^(w-1))
=u-2^w (u>=2^(w-1))
p52
无符号数的截断结果 B2U([x(k-1),x(k-2)...x0])=B2U([x(w-1)...x0])mod 2^k
补码数字截断结果 B2T([x(k-1),x(k-2)...x0])=U2T(B2U([x(w-1)...x0])mod 2^k)
p55
无符号加法 x+(u,w)y= x+y (x+y<2^w)
= x+y-2^w (2^w<=x+y<=2^(w+1))
p58
补码加法 x+(t,w)y= x+y-2^w (2^(w-1)<=x+y) 正溢出
=x+y (-2^(w-1)<=x+y<2^(w-1)) 正常
=x+y+2^w (x+y<-2^(w-1)) 负溢出
p64
乘积移位操作:x*K,让K用二进制数表示,假设K可表示为一组从位位置n到位位置m的连续的1(n>=m),我们可以用下面两种不同形式中的一种来计算这些位对乘积的影响
形式A:(x<
p333 p338 p351 p354 p359 p369 p374 p402-403 p433 p455 p492-522 信号 非本地跳转 p582-585 A-间接引用坏指针 B-读未初始化的存储器 C-允许栈缓冲区溢出 D-假设指针和它们指向的对象是相同大小的 E-造成错位错误 F-引用指针,而不是它指向的对象 G-误解指针运算 H-引用不存在的变量 I-引用空闲堆块中的数据 J-引起存储器泄露 p597-608 p606 p619-629 int inet_aton(const char *cp,struct in_addr *inp)//将一个点分十进制串cp转换为一个网络字节顺序的IP地址inp struct hostent *gethostbyname(const char *name)//返回和域名name相关的主机条目 int socket(int domain, int type, int protocol)//创建一个套接字描述符 p659-660 后记: 只能说这本书可能并没有像吹的那么牛叉,不知道是不是我能力还未到家的缘故,不过这本书讲解的倒是很清晰,其中也有很多编程忠告,在我看来有把计算机组成原理和操作系统结合的因素,不过显然无法将那两大块都包括其中,只能算是一个铺垫,后续深入的学习还是应该阅读更专业的书籍,例如《操作系统-精髓与设计原理》《计算机体系结构》这些。 不知道自己现在看这本书算不算迟了,不过既然能学到东西,我是欣然接受的,多读书,读好书,那肯定是程序员必须要学会的。
代码移动:这类优化包括识别要执行多次但是计算结果不会改变的计算,将计算移动到代码前面不会多次求值的部分
例如:for (int i=0;i
循环展开:这是一种程序变换,通过增加每次迭代计算的元素的数量,减少循环的迭代次数
例如:
for(i=0;i
上面就是展开循环K=2次,不过最后几个元素需要单独处理
两路并行:对于可结合和可交换的合并运算来说,可以通过将一组合并运算分割成两个或更多部分,并在最后合并结果来提高性能
例如:
for(i=0;i
acc0=acc0 OP data[i];
acc1=acc1 OP data[i+1];
}
...
*dest=acc0 OP acc1
上面利用了两次循环展开以及两路并行
重新结合变换:对代码做很小的改动,改变合并方式,能够减少计算中关键路径上操作的数量,通过更好地利用功能单元的流水线能力得到更好的性能
例如:
acc=(acc OP data[i]) OP data[i+1]
acc=acc OP(data[i] OP data[i+1])
仅仅是括号的差别,但是性能却大不一样
关键路径指明了执行某程序所需时间的一个基本的下界,如果某程序存在数据相关链,这条链上所有延迟之和等于T,那么这个程序至少需要T个周期才能执行完
同时功能单元的吞吐量界限也是程序执行的一个下界,假设一个程序一共需要N个某种运算的计算,而微处理器只有m个能够执行这个操作的功能单元,并且这些单元的发射时间为i,那么该程序执行至少需要N*i/m个周期
性能提高技术
A-高级设计 选择适当的算法和数据结构
B-基本编码原则
消除连续的函数调用,尽量将计算移到循环外
消除不必要的存储器引用,引入临时变量来保存中间结构,只有最后计算出的值才保存在数组或全局变量中
C-低级优化
展开循环,降低开销,并且使得进一步的优化成为可能
通过使用例如多个累计变量和重新结合等技术,找到方法提高指令级并行
用功能的风格重写条件操作,使得编译采用条件数据传送
Amdahl定律:当我们加快系统一个部分的速度时,对系统整体性能的影响依赖于这个部分有多重要和速度提高了多少
假设系统某个部分需要整个应用程序执行时间的百分比为α,则
加速比 S=1/[(1-α)+α/k]
所以要想大幅度提高整个系统的速度,必须提高整个系统很大一部分的速度
局部性:时间局部性(重复引用),空间局部性(引用附近的一个位置)
步长为1的引用模式为顺序引用模式,一般而言,随着步长的增加,空间局部性下降
评价局部性原则:
重复引用同一个变量的程序有良好的时间局部性
对于步长为k的引用模式的程序,步长越小,空间局部性越好。步长为1有很好的空间局部性,使用大步长的程序空间局部性很差
对于取指令来说,循环有好的时间和空间局部性,循环迭代次数越多,局部性就越好
使用下列技术利用局部性:
将你的注意力集中在内循环上,大部分计算和存储器的访问都发生在那里
通过按照数据对象存储在存储器中的顺序、以步长为1的来读数据,从而使得你程序中的空间局部性最大
一旦从存储器中读入了一个数据对象,仅尽可能多地使用它,从而使得程序中的时间局部性最大
函数和已初始化的全局变量为强符号,未初始化的全局变量为弱符号
unix链接器使用下面规则来处理多重定义的符号:
规则1:不允许有多个强符号
规则2:如果有一个强符号和多个弱符号,那么就选择强符号
规则3:如果有多个弱符号,那么就从这些弱符号中任意选择一个
进程函数
pid_t getpid(void) //返回调用进程的PID
pid_t getppid(void) //返回父进程PID
void exit(int status) //终止进程
pid_t fork(void) //父进程通过此函数创建子进程,父进程中fork返回子进程PID,在子进程中返回0
pid_t waitpid(pid_t pid, int *status, int options)//父进程等待子进程终止或停止 pid>0等待单独子进程, pid=-1为全部
pid_t wait(int *status) //等待所有子进程
unsigned int sleep(unsigned int secs)//将一个进程挂起一段指定的时间
int pause(void) //让调用函数休眠,直到该进程收到一个信号
int execve(const char *filename, const char *agrv[], const char *envp[])//函数加载运行一个新程序
char *getenv(const char *name)//在环境数组中搜索字符串name=value,找到返回一个指向value指针
int setenv(const char *name, const char *newvalue, int overwrite)//用newvalue代替oldvalue
void unsetenv(const char *name)//删除name=value
pid_t getpgrp(void)//返回当前进程的进程组ID(每个进程都只属于一个进程组)
int setpgid(pid_t pid,pid_t,pgid)//改变自己或其它进程的进程组
int kill(pid_t pid, int sig)//终止进程,发送信号sig给进程pid
unsigned int alarm(unsigned int secs)//进程在secs秒内向自身发sigalam信号,
sighandler_t signal(int signum, sighandler_t handler)//进程接收信号
int sigaction(int signum,struct sigaction *act,struct sigaction *oldact)//信号处理
int setjmp(jum_buf env) //在env缓冲区中保存当前调用环境,为longjmp使用
int sigsetjmp(sigjmp_buf env,int savesigs)//信号处理使用版本
void longjmp(jum_buf env,int retval)//从env缓冲区中恢复调用环境
void siglongjmp(sigjmp_buf env,int retval)//信号处理使用版本
C程序中常见的与存储器有关的错误
例如:scanf("%d",val)
例如:假设堆存储器被初始化为零
int *y=(int *)malloc(n*sizeof(int))
其实这里并未初始化,使用calloc才能实现
例如:char buf[64]; gets(buf)
例如:int **A=(int **)malloc(n*sizeof(int)); //应该为sizeof(int *)
... A[i]=(int *)malloc(m*sizeof(int))
程序目的创建一个由n个指针组成的数组,每个指针都指向一个包含m个int的数组,但是代码实际创建的是一个int数组
例如:for(int i=0;i<=m;i++) //考虑=号是否取到
例如:binheap[0]=binheap[*size-1];
*size--; //应该为(*size)--
例如: p为指针
p+=sizeof(int) //应该为p++
例如: int val;
return &val; //程序使用一次后,本地变量不再合法
例如: free(x);
y=x;
例如: int *x=(int *)malloc (n*sizeof(int)); //使用后没有free
UNIX系统级I/O
int open(char *filename,int flags, mode_t mode)//打开文件或创建一个新文件
int close(int fd)// 关闭文件
ssize_t read(int fd,void *buf, size_t n)//从描述符fd的文件位置拷贝最多n个字节到存储器buf
ssize_t write(int fd,const void *buf, size_t n)//从存储器拷贝最多n个字符到文件
int stat(const char *filename, struct stat *buf)//读取文件元数据
int fstat(int fd,struct stat *buf)//同上
int dup2(int oldfd,int newfd)//I/O重定向,拷贝描述符表项oldfd到描述符表项newfd
UNIX内核用三个相关的数据结构来表示打开的文件
描述符表:每个进程都有自己独立的描述符表,表项由进程打开的文件描述符来索引
文件表:打开文件的集合有一张文件表来表示,所有的进程共享这张表
v-node表:同文件表一样,所有进程共享这张v-node表,每个表项包含stat结构中的大多数信息
UNIX网络编程函数
unsigned long int htonl(unsigned long int hostlong)//将32位整数由主机字节顺序转换为网络字节顺序
unsigned short int htons(unsigned short int hostshort)//将16位整数由主机字节顺序转换为网络字节顺序
unsigned long int ntohl(unsigned long int netlong)//将32位整数由网络字节顺序转换为主机字节顺序
unsigned short int ntohs(unsigned short int netshort)//将16位整数由网络字节顺序转换为主机字节顺序
char *inet_ntoa(struct in_addr_in)//相逆操作,这里传递的是结构本身,不是指向结构的指针
struct hostent *gethostbyaddr(const char *addr, int len,0)//返回和IP地址addr相关联的主机条目
int connect(int sockfd,struct sockaddr *serv_addr, int addrlen)//试图与套接字地址为serv_addr的服务器建立一个因特网连接
int bind(int sockfd,struct sockaddr *my_addr, int addrlen)//告诉内核将my_addr中的服务器套接字地址和套接字描述符sockfd联系起来
int listen(int sockfd, int backlog)//将sockfd从一个主动的套接字转化为一个监听套接字,接收来自客户端的连接请求
int accept(int listenfd,struct sockaddr *addr,int *addrlen)//服务器通过此函数等待客户端的连接请求
线程函数
int pthread_create(pthread_t *tid,pthread_attr_t *attr,func *f,void *arg)//创建一个新线程
pthread_t pthread_self(void) //获得自己线程的ID
void pthread_exit(void *thread_return) //线程显示终止,主线程调用等待对等线程终止
int pthread_cancel(pthread_t tid)//终止当前线程
int pthread_join(pthread_t tid,void **thread_return)//等待其他线程终止
int pthread_detach(pthread_t tid)//线程调用此函数分离可结合线程tid
int pthread_once(pthread_once_t *once_control, void(*init_routine)(void))//初始化与线程相关的状态