摘要内容……

阅读全文 »
l

一个函数秒杀 2Sum 3Sum 4Sum 问题

经常刷 LeetCode 的读者肯定知道鼎鼎有名的 twoSum 问题,我们的旧文 Two Sum 问题的核心思想twoSum 的几个变种做了解析。

但是除了 twoSum 问题,LeetCode 上面还有 3Sum4Sum 问题,我估计以后出个 5Sum6Sum 也不是不可能。

那么,对于这种问题有没有什么好办法用套路解决呢?本文就由浅入深,层层推进,用一个函数来解决所有 nSum 类型的问题。

一、twoSum 问题

[leetcode1](1. 两数之和 - 力扣(LeetCode) (leetcode-cn.com))

力扣上的 twoSum 问题,题目要求返回的是索引,这里我来编一道 twoSum 题目,不要返回索引,返回元素的值:

如果假设输入一个数组 nums 和一个目标和 target请你返回 nums 中能够凑出 target 的两个元素的值,比如输入 nums = [5,3,1,6], target = 9,那么算法返回两个元素 [3,6]。可以假设只有且仅有一对儿元素可以凑出 target

我们可以先对 nums 排序,然后利用前文「双指针技巧汇总」写过的左右双指针技巧,从两端相向而行就行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<int> twoSum(vector<int>& nums, int target) {
// 先对数组排序
sort(nums.begin(), nums.end());
// 左右指针
int lo = 0, hi = nums.size() - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// 根据 sum 和 target 的比较,移动左右指针
if (sum < target) {
lo++;
} else if (sum > target) {
hi--;
} else if (sum == target) {
return {nums[lo], nums[hi]};
}
}
return {};
}

这样就可以解决这个问题,不过我们要继续魔改题目,把这个题目变得更泛化,更困难一点:

nums 中可能有多对儿元素之和都等于 target,请你的算法返回所有和为 target 的元素对儿,其中不能出现重复

函数签名如下:

1
vector<vector<int>> twoSumTarget(vector<int>& nums, int target);

比如说输入为 nums = [1,3,1,2,2,3], target = 4,那么算法返回的结果就是:[[1,3],[2,2]]

对于修改后的问题,关键难点是现在可能有多个和为 target 的数对儿,还不能重复,比如上述例子中 [1,3][3,1] 就算重复,只能算一次。

首先,基本思路肯定还是排序加双指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<vector<int>> twoSumTarget(vector<int>& nums, int target {
// 先对数组排序
sort(nums.begin(), nums.end());
vector<vector<int>> res;
int lo = 0, hi = nums.size() - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// 根据 sum 和 target 的比较,移动左右指针
if (sum < target) lo++;
else if (sum > target) hi--;
else {
res.push_back({lo, hi});
lo++; hi--;
}
}
return res;
}

但是,这样实现会造成重复的结果,比如说 nums = [1,1,1,2,2,3,3], target = 4,得到的结果中 [1,3] 肯定会重复。

出问题的地方在于 sum == target 条件的 if 分支,当给 res 加入一次结果后,lohi 不应该改变 1 的同时,还应该跳过所有重复的元素:

图片

1
2
3
4
5
6
7
8
9
10
11
12
13
while (lo < hi) {
int sum = nums[lo] + nums[hi];
// 记录索引 lo 和 hi 最初对应的值
int left = nums[lo], right = nums[hi];
if (sum < target) lo++;
else if (sum > target) hi--;
else {
res.push_back({left, right});
// 跳过所有重复的元素
while (lo < hi && nums[lo] == left) lo++;
while (lo < hi && nums[hi] == right) hi--;
}
}

这样就可以保证一个答案只被添加一次,重复的结果都会被跳过,可以得到正确的答案。不过,受这个思路的启发,其实前两个 if 分支也是可以做一点效率优化,跳过相同的元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vector<vector<int>> twoSumTarget(vector<int>& nums, int target) {
// nums 数组必须有序
sort(nums.begin(), nums.end());
int lo = 0, hi = nums.size() - 1;
vector<vector<int>> res;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
int left = nums[lo], right = nums[hi];
if (sum < target) {
while (lo < hi && nums[lo] == left) lo++;
} else if (sum > target) {
while (lo < hi && nums[hi] == right) hi--;
} else {
res.push_back({left, right});
while (lo < hi && nums[lo] == left) lo++;
while (lo < hi && nums[hi] == right) hi--;
}
}
return res;
}

这样,一个通用化的 twoSum 函数就写出来了,请确保你理解了该算法的逻辑,我们后面解决 3Sum4Sum 的时候会复用这个函数。

这个函数的时间复杂度非常容易看出来,双指针操作的部分虽然有那么多 while 循环,但是时间复杂度还是 O(N),而排序的时间复杂度是 O(NlogN),所以这个函数的时间复杂度是 O(NlogN)

二、3Sum 问题

Leetcode15

这是力扣第 15 题「三数之和」:

图片

题目就是让我们找 nums 中和为 0 的三个元素,返回所有可能的三元组(triple),函数签名如下:

1
vector<vector<int>> threeSum(vector<int>& nums);

这样,我们再泛化一下题目,不要光和为 0 的三元组了,计算和为 target 的三元组吧,同上面的 twoSum 一样,也不允许重复的结果:

1
2
3
4
5
6
7
8
vector<vector<int>> threeSum(vector<int>& nums) {
// 求和为 0 的三元组
return threeSumTarget(nums, 0);
}

vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
// 输入数组 nums,返回所有和为 target 的三元组
}

这个问题怎么解决呢?很简单,穷举呗。现在我们想找和为 target 的三个数字,那么对于第一个数字,可能是什么?nums 中的每一个元素 nums[i] 都有可能!

那么,确定了第一个数字之后,剩下的两个数字可以是什么呢?其实就是和为 target - nums[i] 的两个数字呗,那不就是 twoSum 函数解决的问题么🤔

可以直接写代码了,需要把 twoSum 函数稍作修改即可复用:

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
/* 从 nums[start] 开始,计算有序数组
* nums 中所有和为 target 的二元组 */
vector<vector<int>> twoSumTarget(
vector<int>& nums, int start, int target) {
// 左指针改为从 start 开始,其他不变
int lo = start, hi = nums.size() - 1;
vector<vector<int>> res;
while (lo < hi) {
...
}
return res;
}

/* 计算数组 nums 中所有和为 target 的三元组 */
vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
// 数组得排个序
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>> res;
// 穷举 threeSum 的第一个数
for (int i = 0; i < n; i++) {
// 对 target - nums[i] 计算 twoSum
vector<vector<int>>
tuples = twoSumTarget(nums, i + 1, target - nums[i]);
// 如果存在满足条件的二元组,再加上 nums[i] 就是结果三元组
for (vector<int>& tuple : tuples) {
tuple.push_back(nums[i]);
res.push_back(tuple);
}
// 跳过第一个数字重复的情况,否则会出现重复结果
while (i < n - 1 && nums[i] == nums[i + 1]) i++;
}
return res;
}

需要注意的是,类似 twoSum3Sum 的结果也可能重复,比如输入是 nums = [1,1,1,2,3], target = 6,结果就会重复。

关键点在于,不能让第一个数重复,至于后面的两个数,我们复用的 twoSum 函数会保证它们不重复。所以代码中必须用一个 while 循环来保证 3Sum 中第一个元素不重复。

至此,3Sum 问题就解决了,时间复杂度不难算,排序的复杂度为 O(NlogN)twoSumTarget 函数中的双指针操作为 O(N)threeSumTarget 函数在 for 循环中调用 twoSumTarget 所以总的时间复杂度就是 O(NlogN + N^2) = O(N^2)

三、4Sum 问题

Leetcode18

这是力扣第 18 题「四数之和」:

图片

函数签名如下:

1
vector<vector<int>> fourSum(vector<int>& nums, int target);

都到这份上了,4Sum 完全就可以用相同的思路:穷举第一个数字,然后调用 3Sum 函数计算剩下三个数,最后组合出和为 target 的四元组。

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
vector<vector<int>> fourSum(vector<int>& nums, int target) {
// 数组需要排序
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>> res;
// 穷举 fourSum 的第一个数
for (int i = 0; i < n; i++) {
// 对 target - nums[i] 计算 threeSum
vector<vector<int>>
triples = threeSumTarget(nums, i + 1, target - nums[i]);
// 如果存在满足条件的三元组,再加上 nums[i] 就是结果四元组
for (vector<int>& triple : triples) {
triple.push_back(nums[i]);
res.push_back(triple);
}
// fourSum 的第一个数不能重复
while (i < n - 1 && nums[i] == nums[i + 1]) i++;
}
return res;
}

/* 从 nums[start] 开始,计算有序数组
* nums 中所有和为 target 的三元组 */
vector<vector<int>>
threeSumTarget(vector<int>& nums, int start, int target) {
int n = nums.size();
vector<vector<int>> res;
// i 从 start 开始穷举,其他都不变
for (int i = start; i < n; i++) {
...
}
return res;

这样,按照相同的套路,4Sum 问题就解决了,时间复杂度的分析和之前类似,for 循环中调用了 threeSumTarget 函数,所以总的时间复杂度就是 O(N^3)

四、100Sum 问题?

在 LeetCode 上,4Sum 就到头了,但是回想刚才写 3Sum4Sum 的过程,实际上是遵循相同的模式的。我相信你只要稍微修改一下 4Sum 的函数就可以复用并解决 5Sum 问题,然后解决 6Sum 问题……

那么,如果我让你求 100Sum 问题,怎么办呢?其实我们可以观察上面这些解法,统一出一个 nSum 函数:

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
36
37
38
39
40
/* 注意:调用这个函数之前一定要先给 nums 排序 */
vector<vector<int>> nSumTarget(
vector<int>& nums, int n, int start, int target) {

int sz = nums.size();
vector<vector<int>> res;
// 至少是 2Sum,且数组大小不应该小于 n
if (n < 2 || sz < n) return res;
// 2Sum 是 base case
if (n == 2) {
// 双指针那一套操作
int lo = start, hi = sz - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
int left = nums[lo], right = nums[hi];
if (sum < target) {
while (lo < hi && nums[lo] == left) lo++;
} else if (sum > target) {
while (lo < hi && nums[hi] == right) hi--;
} else {
res.push_back({left, right});
while (lo < hi && nums[lo] == left) lo++;
while (lo < hi && nums[hi] == right) hi--;
}
}
} else {
// n > 2 时,递归计算 (n-1)Sum 的结果
for (int i = start; i < sz; i++) {
vector<vector<int>>
sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
for (vector<int>& arr : sub) {
// (n-1)Sum 加上 nums[i] 就是 nSum
arr.push_back(nums[i]);
res.push_back(arr);
}
while (i < sz - 1 && nums[i] == nums[i + 1]) i++;
}
}
return res;
}

嗯,看起来很长,实际上就是把之前的题目解法合并起来了,n == 2 时是 twoSum 的双指针解法,n > 2 时就是穷举第一个数字,然后递归调用计算 (n-1)Sum,组装答案。

需要注意的是,调用这个 nSum 函数之前一定要先给 nums 数组排序,因为 nSum 是一个递归函数,如果在 nSum 函数里调用排序函数,那么每次递归都会进行没有必要的排序,效率会非常低。

比如说现在我们写 LeetCode 上的 4Sum 问题:

1
2
3
4
5
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
// n 为 4,从 nums[0] 开始计算和为 target 的四元组
return nSumTarget(nums, 4, 0, target);
}

再比如 LeetCode 的 3Sum 问题,找 target == 0 的三元组:

1
2
3
4
5
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
// n 为 3,从 nums[0] 开始计算和为 0 的三元组
return nSumTarget(nums, 3, 0, 0);
}

那么,如果让你计算 100Sum 问题,直接调用这个函数就完事儿了。

l

如何查看 Linux 服务器性能参数指标?

一个基于 Linux 操作系统的服务器运行的同时,也会表征出各种各样参数信息。通常来说运维人员、系统管理员会对这些数据会极为敏感,但是这些参数对于开发者来说也十分重要,尤其当你的程序非正常工作的时候,这些蛛丝马迹往往会帮助快速定位跟踪问题。这里只是一些简单的工具查看系统的相关参数,当然很多工具也是通过分析加工 /proc、/sys 下的数据来工作的,而那些更加细致、专业的性能监测和调优,可能还需要更加专业的工具(perf、systemtap 等)和技术才能完成哦。毕竟来说,系统性能监控本身就是个大学问。

img

一、CPU和内存类

1.1 top

➜ ~ top

img

第一行后面的三个值是系统在之前 1、5、15 的平均负载,也可以看出系统负载是上升、平稳、下降的趋势,当这个值超过 CPU 可执行单元的数目,则表示 CPU 的性能已经饱和成为瓶颈了。

第二行统计了系统的任务状态信息。running 很自然不必多说,包括正在 CPU 上运行的和将要被调度运行的;sleeping 通常是等待事件(比如 IO 操作)完成的任务,细分可以包括 interruptible 和 uninterruptible 的类型;stopped 是一些被暂停的任务,通常发送 SIGSTOP 或者对一个前台任务操作 Ctrl-Z 可以将其暂停;zombie 僵尸任务,虽然进程终止资源会被自动回收,但是含有退出任务的 task descriptor 需要父进程访问后才能释放,这种进程显示为 defunct 状态,无论是因为父进程提前退出还是未 wait 调用,出现这种进程都应该格外注意程序是否设计有误。

第三行 CPU 占用率根据类型有以下几种情况:

√ (us) user:CPU 在低 nice 值(高优先级)用户态所占用的时间(nice<=0)。正常情况下只要服务器不是很闲,那么大部分的 CPU 时间应该都在此执行这类程序

√ (sy) system:CPU 处于内核态所占用的时间,操作系统通过系统调用(system call)从用户态陷入内核态,以执行特定的服务;通常情况下该值会比较小,但是当服务器执行的 IO 比较密集的时候,该值会比较大

√ (ni) nice:CPU 在高 nice 值(低优先级)用户态以低优先级运行占用的时间(nice>0)。默认新启动的进程 nice=0,是不会计入这里的,除非手动通过 renice 或者 setpriority() 的方式修改程序的nice值

√ (id) idle:CPU 在空闲状态(执行 kernel idle handler )所占用的时间

√ (wa) iowait:等待 IO 完成做占用的时间

√ (hi) irq:系统处理硬件中断所消耗的时间

√ (si) softirq:系统处理软中断所消耗的时间,记住软中断分为 softirqs、tasklets (其实是前者的特例)、work queues,不知道这里是统计的是哪些的时间,毕竟 work queues 的执行已经不是中断上下文了

√ (st) steal:在虚拟机情况下才有意义,因为虚拟机下 CPU 也是共享物理 CPU 的,所以这段时间表明虚拟机等待 hypervisor 调度 CPU 的时间,也意味着这段时间 hypervisor 将 CPU 调度给别的 CPU 执行,这个时段的 CPU 资源被“stolen”了。这个值在我 KVM 的 VPS 机器上是不为 0 的,但也只有 0.1 这个数量级,是不是可以用来判断 VPS 超售的情况?

CPU 占用率高很多情况下意味着一些东西,这也给服务器 CPU 使用率过高情况下指明了相应地排查思路:

√ 当 user 占用率过高的时候,通常是某些个别的进程占用了大量的 CPU,这时候很容易通过 top 找到该程序;此时如果怀疑程序异常,可以通过 perf 等思路找出热点调用函数来进一步排查;

√ 当 system 占用率过高的时候,如果 IO 操作(包括终端 IO)比较多,可能会造成这部分的 CPU 占用率高,比如在 file server、database server 等类型的服务器上,否则(比如>20%)很可能有些部分的内核、驱动模块有问题;

√ 当 nice 占用率过高的时候,通常是有意行为,当进程的发起者知道某些进程占用较高的 CPU,会设置其 nice 值确保不会淹没其他进程对 CPU 的使用请求;

√ 当 iowait 占用率过高的时候,通常意味着某些程序的 IO 操作效率很低,或者 IO 对应设备的性能很低以至于读写操作需要很长的时间来完成;

√ 当 irq/softirq 占用率过高的时候,很可能某些外设出现问题,导致产生大量的irq请求,这时候通过检查 /proc/interrupts 文件来深究问题所在;

√ 当 steal 占用率过高的时候,黑心厂商虚拟机超售了吧!

第四行和第五行是物理内存和虚拟内存(交换分区)的信息:

total = free + used + buff/cache,现在buffers和cached Mem信息总和到一起了,但是buffers和cached Mem 的关系很多地方都没说清楚。其实通过对比数据,这两个值就是 /proc/meminfo 中的 Buffers 和 Cached 字段:Buffers 是针对 raw disk 的块缓存,主要是以 raw block 的方式缓存文件系统的元数据(比如超级块信息等),这个值一般比较小(20M左右);而 Cached 是针对于某些具体的文件进行读缓存,以增加文件的访问效率而使用的,可以说是用于文件系统中文件缓存使用。

而 avail Mem 是一个新的参数值,用于指示在不进行交换的情况下,可以给新开启的程序多少内存空间,大致和 free + buff/cached 相当,而这也印证了上面的说法,free + buffers + cached Mem才是真正可用的物理内存。并且,使用交换分区不见得是坏事情,所以交换分区使用率不是什么严重的参数,但是频繁的 swap in/out 就不是好事情了,这种情况需要注意,通常表示物理内存紧缺的情况。

最后是每个程序的资源占用列表,其中 CPU 的使用率是所有 CPU core 占用率的总和。通常执行 top 的时候,本身该程序会大量的读取 /proc 操作,所以基本该 top 程序本身也会是名列前茅的。

top 虽然非常强大,但是通常用于控制台实时监测系统信息,不适合长时间(几天、几个月)监测系统的负载信息,同时对于短命的进程也会遗漏无法给出统计信息。

1.2 vmstat

vmstat 是除 top 之外另一个常用的系统检测工具,下面截图是我用-j4编译boost的系统负载。

img

r 表示可运行进程数目,数据大致相符;而b表示的是 uninterruptible 睡眠的进程数目;swpd 表示使用到的虚拟内存数量,跟 top-Swap-used 的数值是一个含义,而如手册所说,通常情况下 buffers 数目要比 cached Mem 小的多,buffers 一般20M这么个数量级;io 域的 bi、bo 表明每秒钟向磁盘接收和发送的块数目(blocks/s);system 域的 in 表明每秒钟的系统中断数(包括时钟中断),cs表明因为进程切换导致上下文切换的数目。

说到这里,想到以前很多人纠结编译 linux kernel 的时候 -j 参数究竟是 CPU Core 还是 CPU Core+1?通过上面修改 -j 参数值编译 boost 和 linux kernel 的同时开启 vmstat 监控,发现两种情况下 context switch 基本没有变化,且也只有显著增加 -j 值后 context switch 才会有显著的增加,看来不必过于纠结这个参数了,虽然具体编译时间长度我还没有测试。资料说如果不是在系统启动或者 benchmark 的状态,参数 context switch>100000 程序肯定有问题。

1.3 pidstat

如果想对某个进程进行全面具体的追踪,没有什么比 pidstat 更合适的了——栈空间、缺页情况、主被动切换等信息尽收眼底。这个命令最有用的参数是-t,可以将进程中各个线程的详细信息罗列出来。

-r:显示缺页错误和内存使用状况,缺页错误是程序需要访问映射在虚拟内存空间中但是还尚未被加载到物理内存中的一个分页,缺页错误两个主要类型是

√ minflt/s 指的 minor faults,当需要访问的物理页面因为某些原因(比如共享页面、缓存机制等)已经存在于物理内存中了,只是在当前进程的页表中没有引用,MMU 只需要设置对应的 entry 就可以了,这个代价是相当小的

√ majflt/s 指的 major faults,MMU 需要在当前可用物理内存中申请一块空闲的物理页面(如果没有可用的空闲页面,则需要将别的物理页面切换到交换空间去以释放得到空闲物理页面),然后从外部加载数据到该物理页面中,并设置好对应的 entry,这个代价是相当高的,和前者有几个数据级的差异

-s:栈使用状况,包括 StkSize 为线程保留的栈空间,以及 StkRef 实际使用的栈空间。使用ulimit -s发现CentOS 6.x上面默认栈空间是10240K,而 CentOS 7.x、Ubuntu系列默认栈空间大小为8196K

img

-u:CPU使用率情况,参数同前面类似

-w:线程上下文切换的数目,还细分为cswch/s因为等待资源等因素导致的主动切换,以及nvcswch/s线程CPU时间导致的被动切换的统计

如果每次都先ps得到程序的pid后再操作pidstat会显得很麻烦,所以这个杀手锏的-C可以指定某个字符串,然后Command中如果包含这个字符串,那么该程序的信息就会被打印统计出来,-l可以显示完整的程序名和参数

➜ ~ pidstat -w -t -C “ailaw” -l

这么看来,如果查看单个尤其是多线程的任务时候,pidstat比常用的ps更好使!

1.4 其他

当需要单独监测单个 CPU 情况的时候,除了 htop 还可以使用 mpstat,查看在 SMP 处理器上各个 Core 的工作量是否负载均衡,是否有某些热点线程占用 Core。

➜ ~ mpstat -P ALL 1

如果想直接监测某个进程占用的资源,既可以使用top -u taozj的方式过滤掉其他用户无关进程,也可以采用下面的方式进行选择,ps命令可以自定义需要打印的条目信息:

while :; do ps -eo user,pid,ni,pri,pcpu,psr,comm | grep ‘ailawd’; sleep 1; done

如想理清继承关系,下面一个常用的参数可以用于显示进程树结构,显示效果比pstree详细美观的多

➜ ~ ps axjf

二、磁盘IO类

iotop 可以直观的显示各个进程、线程的磁盘读取实时速率;lsof 不仅可以显示普通文件的打开信息(使用者),还可以操作 /dev/sda1 这类设备文件的打开信息,那么比如当分区无法 umount 的时候,就可以通过 lsof 找出磁盘该分区的使用状态了,而且添加 +fg 参数还可以额外显示文件打开 flag 标记。

2.1 iostat

➜ ~ iostat -xz 1

其实无论使用 iostat -xz 1 还是使用 sar -d 1,对于磁盘重要的参数是:

√ avgqu-s:发送给设备 I/O 请求的等待队列平均长度,对于单个磁盘如果值>1表明设备饱和,对于多个磁盘阵列的逻辑磁盘情况除外

√ await(r_await、w_await):平均每次设备 I/O 请求操作的等待时间(ms),包含请求排列在队列中和被服务的时间之和;

√ svctm:发送给设备 I/O 请求的平均服务时间(ms),如果 svctm 与 await 很接近,表示几乎没有 I/O 等待,磁盘性能很好,否则磁盘队列等待时间较长,磁盘响应较差;

√ %util:设备的使用率,表明每秒中用于 I/O 工作时间的占比,单个磁盘当 %util>60% 的时候性能就会下降(体现在 await 也会增加),当接近100%时候就设备饱和了,但对于有多个磁盘阵列的逻辑磁盘情况除外;

还有,虽然监测到的磁盘性能比较差,但是不一定会对应用程序的响应造成影响,内核通常使用 I/O asynchronously 技术,使用读写缓存技术来改善性能,不过这又跟上面的物理内存的限制相制约了。

上面的这些参数,对网络文件系统也是受用的。

三、网络类

网络性能对于服务器的重要性不言而喻,工具 iptraf 可以直观的现实网卡的收发速度信息,比较的简洁方便通过 sar -n DEV 1 也可以得到类似的吞吐量信息,而网卡都标配了最大速率信息,比如百兆网卡千兆网卡,很容易查看设备的利用率。

通常,网卡的传输速率并不是网络开发中最为关切的,而是针对特定的 UDP、TCP 连接的丢包率、重传率,以及网络延时等信息。

3.1 netstat

➜ ~ netstat -s

显示自从系统启动以来,各个协议的总体数据信息。虽然参数信息比较丰富有用,但是累计值,除非两次运行做差才能得出当前系统的网络状态信息,亦或者使用 watch 眼睛直观其数值变化趋势。所以netstat通常用来检测端口和连接信息的:

netstat –all(a) –numeric(n) –tcp(t) –udp(u) –timers(o) –listening(l) –program(p)

–timers可以取消域名反向查询,加快显示速度;比较常用的有

➜ ~ netstat -antp #列出所有TCP的连接
➜ ~ netstat -nltp #列出本地所有TCP侦听套接字,不要加-a参数

3.2 sar

sar 这个工具太强大了,什么 CPU、磁盘、页面交换啥都管,这里使用 -n 主要用来分析网络活动,虽然网络中它还给细分了 NFS、IP、ICMP、SOCK 等各种层次各种协议的数据信息,我们只关心 TCP 和 UDP。下面的命令除了显示常规情况下段、数据报的收发情况,还包括

TCP

➜ ~ sudo sar -n TCP,ETCP 1

img

√ active/s:本地发起的 TCP 连接,比如通过 connect(),TCP 的状态从CLOSED -> SYN-SENT

√ passive/s:由远程发起的 TCP 连接,比如通过 accept(),TCP 的状态从LISTEN -> SYN-RCVD

√ retrans/s(tcpRetransSegs):每秒钟 TCP 重传数目,通常在网络质量差,或者服务器过载后丢包的情况下,根据 TCP 的确认重传机制会发生重传操作

√ isegerr/s(tcpInErrs):每秒钟接收到出错的数据包(比如 checksum 失败)

UDP
➜ ~ sudo sar -n UDP 1

√ noport/s(udpNoPorts):每秒钟接收到的但是却没有应用程序在指定目的端口的数据报个数

√ idgmerr/s(udpInErrors):除了上面原因之外的本机接收到但却无法派发的数据报个数

当然,这些数据一定程度上可以说明网络可靠性,但也只有同具体的业务需求场景结合起来才具有意义。

3.3 tcpdump

tcpdump 不得不说是个好东西。大家都知道本地调试的时候喜欢使用 wireshark,但是线上服务端出现问题怎么弄呢?

附录的参考文献给出了思路:复原环境,使用 tcpdump 进行抓包,当问题复现(比如日志显示或者某个状态显现)的时候,就可以结束抓包了,而且 tcpdump 本身带有 -C/-W 参数,可以限制抓取包存储文件的大小,当达到这个这个限制的时候保存的包数据自动 rotate,所以抓包数量总体还是可控的。此后将数据包拿下线来,用 wireshark 想怎么看就怎么看,岂不乐哉!tcpdump 虽然没有 GUI 界面,但是抓包的功能丝毫不弱,可以指定网卡、主机、端口、协议等各项过滤参数,抓下来的包完整又带有时间戳,所以线上程序的数据包分析也可以这么简单。

下面就是一个小的测试,可见 Chrome 启动时候自动向 Webserver 发起建立了三条连接,由于这里限制了 dst port 参数,所以服务端的应答包被过滤掉了,拿下来用 wireshark 打开,SYNC、ACK 建立连接的过程还是很明显的!在使用 tcpdump 的时候,需要尽可能的配置抓取的过滤条件,一方面便于接下来的分析,二则 tcpdump 开启后对网卡和系统的性能会有影响,进而会影响到在线业务的性能。

img

欢迎大家一起学习交流

l

ShadowsocksR:SSR一键脚本Ubuntu版

今天把Ubuntu系统的ShadowsocksR/SSR的一键脚本也补齐了,目前已经上传到 Github。CentOS系统请参考: ShadowsocksR/SSR一键脚本

提示:这是自行搭建科学上网环境的第三步,请确认已经做了前两步:

  1. 购买服务器。想要服务器速度快请参考 搬瓦工购买服务器详细教程购买AkkoCloud德国、美西CN2 GIA VPS ,想ip被封后免费换请参考:购买vultr服务器超详细图文教程
  2. 连接到服务器,Windows系统请参考 Bitvise连接Linux服务器教程,mac用户请参考 Mac电脑连接Linux教程

注意:

  1. 如果有域名,强烈建议使用 v2ray带伪装一键脚本,能有效应付近些天的疯狂封杀,提供稳如狗的体验!
  2. BBR换成魔改BBR/BBR Plus/锐速清参考:安装魔改BBR/BBR Plus/锐速(Lotserver)

使用教程

登录到购买好的Linux服务器(windows可参考 Bitvise连接Linux服务器教程,mac用户请参考 Mac电脑连接Linux教程),在终端(黑框框)里输入如下命令:

1
bash <(curl -sL https://raw.githubusercontent.com/hijkpw/scripts/master/ubuntu_install_ssr.sh)

按回车键,屏幕出现“请设置SSR的密码(不输入则随机生成)” 的提示,按照提示设置密码(SSR的密码。例如1234abcd,不是服务器网页后台的密码)、端口(SSR的端口,例如12345,不能是22和80)并选择加密方式。

接下来屏幕上开始疯狂出现一堆你看得懂看不懂的东西,如果安装过程中卡住,请耐心等待几分钟;安装期间网络断开(windows上表现为黑框框中或者顶部标题出现disconnected字样,mac表现为终端出现“closed by remote host”或”broken pipe”),请重新连接后再次执行命令。安装成功的界面如下:

[ubuntu ssr成功提示信息

ubuntu ssr成功提示信息

到此服务端配置完毕,服务器可能会自动重启(没提示重启则不需要),windows终端出现“disconnected”,mac出现“closed by remote host”说明服务器重启了。

SSR一键脚本做了如下事情:

  1. 更新系统到最新版
  2. 安装bbr加速模块
  3. 安装SSR并设置开机启动

客户端下载

接下来是科学上网最后一步:下载客户端,并参考页面中的配置教程进行配置:

ShadowsocksR/SSR windows客户端下载
ShadowsocksR/SSR安卓客户端下载
ShadowsocksR/SSR mac客户端下载
ShadowsocksR/SSR ios客户端下载

下载客户端配置好后,就可以愉快的上外网了!

其他

  1. 查看ssr运行状态/配置:bash <(curl -sL https://raw.githubusercontent.com/hijkpw/scripts/master/ubuntu_install_ssr.sh) info
  2. SSR管理命令:启动:systemctl start shadowsocksR;停止:systemctl stop shadowsocksR;重启:systemctl restart shadowsocksR
  3. 更改密码、端口、混淆参数的最简单方法:重新运行一键脚本;
  4. 升级到最新版:重新运行一键脚本;
  5. 卸载SSR:bash <(curl -sL https://raw.githubusercontent.com/hijkpw/scripts/master/ubuntu_install_ssr.sh) uninstall
l

ZFile Docker 镜像构建过程

前言

下文提供 ZFile 镜像构建过程,供大家参考。

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM ibm-semeru-runtimes:open-8-jre

WORKDIR /root

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone

RUN curl -o app.jar https://c.jun6.net/ZFILE/zfile-release.jar

EXPOSE 8080

ENTRYPOINT java $JAVA_OPTS -Xshareclasses -Xquickstart -jar /root/app.jar

  • FROM ibm-semeru-runtimes:open-8-jre 表示使用的基础镜像,这里选中的这个是 ibmopenj9 jdk8 版本的 jre,这个版本的 jdk 具有内存占用小,启动速度快等优势,且针对 docker环境做过特殊优化。
  • WORKDIR /root 表示工作目录在 /root,下方所有命令将在这个目录下执行
  • RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtimeRUN echo 'Asia/Shanghai' >/etc/timezone 表示设置时区为上海时间,这两个设置的区别参考:https://unix.stackexchange.com/questions/384971/whats-the-difference-between-localtime-and-timezone-files
  • RUN curl -o app.jar https://c.jun6.net/ZFILE/zfile-release.jar 表示下载 zfile 最新版 jar 包,并命名为 app.jar,因为上方工作目录为 /root,所以实际下载到了 /root/app.jar
  • EXPOSE 8080 表示容器可能会使用这个端口,这个只是声明或备注式的,不会实际影响端口情况。
  • ENTRYPOINT java $JAVA_OPTS -Xshareclasses -Xquickstart -jar /root/app.jar 表示启动 /root/app.jar,其中 -Xshareclasses -Xquickstartibmopenj9 jvm 的参数,用来优化内存占用。

build

由于需要同时支持 arm64 架构和 amd64 架构,所以使用的 docker多平台构建,即同时 build 多个架构的镜像。

  1. 由于 Docker 默认的 builder 实例不支持同时指定多个 --platform,我们必须首先创建一个新的 builder 实例。
1
2
docker buildx create --name zfile-builder --driver docker-container

  1. 使用新创建好的 builder 实例
1
2
docker buildx use zfile-builder

  1. 查看已有的 builder 支持构建的架构类型
1
2
docker buildx ls

  1. 安装模拟器(用于模拟其他平台的构建)
1
2
docker run --privileged --rm tonistiigi/binfmt --install all

  1. Dockerfile 所在目录执行构建命令并 pushdocker hub (需提前 docker login 账号)
1
2
docker buildx build --platform linux/arm64,linux/amd64 -t zhaojun1998/zfile:latest --push .

结语

然后在 docker hub 就可以看到推送的镜像了。

[docker-hub_images

后续还会更新如何使用 CI 工具自动化构建镜像的方式。

l

mac catalina 手动安装openssl@3

mac catalina 通过homebrew 安装 openssl 失败,被嫌弃系统太老,make test 测试不通过

I manually installed openssl@3 with the following commands

  1. Download the latest version from https://www.openssl.org/source/openssl-3.1.1.tar.gz
  2. Uncompress the file with double click and open the terminal. Find the created folder and cd openssl-3.1.1
  3. Continue with
    perl ./Configure --prefix=/usr/local --openssldir=/usr/local/openssl no-ssl3 no-ssl3-method no-zlib darwin64-x86_64-cc enable-ec_nistp_64_gcc_128
  4. make
  5. make test (optional) I passed all tests
  6. sudo make install MANDIR=/usr/local/Cellar/openssl@3/3.1.1_1/share/man MANSUFFIX=ssl
  7. openssl version (optional - verification). It should report OpenSSL 3.1.1 30 May 2023 (Library: OpenSSL 3.1.1 30 May 2023)
  8. which -a openssl (optional).
  9. brew link openssl@3
  10. brew list (optional) you should see the openssl@3 inside installed packages.
l

关闭 OneDrive 内部的自动更新机制

我有一台用了十年的 mac mini,这台电脑的质量还是不错的。虽然也升级了内存和硬盘。 这个没怎么清过灰的家伙也一直没坏。稳定地发挥一个 HTPC 的作用。 不过苹果是已经放弃了它,最终它停留在 catalina。 不过最近我遇到了点麻烦:onedrive 打不开了。

目录

本文使用到了 homebrew,如果你没有或不能使用 brew,请参考另一篇:

更新

目前看来,这是微软推送了一个未在 catalina 上验证过的更新,目前问题已修复, 可以直接安装到最新版,如果您已经使用了本文的方法关闭了自动更新, 那么删掉现有 OneDrive 程序重新安装即可。下次微软再犯相同错误,可以如法炮制。

问题

某天,OneDrive 突然提示我它已经损坏了,重启也没什么反应。 我的第一反应是 mini 用的这块 ssd 大限到了, 不过后来看看其他程序和文件似乎也都正常,感觉又不太像是 ssd 的寿命问题。

如果不是 ssd 的寿命问题,之前 OneDrive 也是好好的,那么估计就是更新出问题了, 我的 OneDrive 是在 app store 安装的,打开商店看了一下,最近 OneDrive 果然有更新。

第一回合我们自然是先重装试试看能不能修复,顺带一提, OneDrive 内部有一个重置的命令,可以把 OneDrive 重置到出厂状态, 右键点击 OneDrive 应用,点击「显示包内容」:

img

接下来就可以在 Contents/Resources 下找到 ResetOneDriveApp.command:

img

双击运行就好,不过写在这里你应该可以猜到,这个和重装都没能解决问题。

解决

首先我们要安装旧版本的应用,很显然 app store 是没办法更新到指定版本的, 不过 brew 可以,只是需要一些小手段。

使用 HomeBrew 安装旧版应用

首先我们在 Homebrew Formulae 中找到 onedrive 的页面, 从这个页面我们可以找到 OneDrive 的 Cask code, 点击 history:

img

这时我们就可以看到近期的更新,我打算更新到 2 月 23 日的版本, 我们点击 #141750 右边「查看当时的代码

img

接下来 raw 按钮给出的就是 当时的 cask code 了,我们可以下载下来,使用以下命令安装:

1
brew install --cask onedrive.rb

安装好以后,千万不要立即执行 OneDrive!因为 OneDrive 自身也内嵌了更新机制, 一旦启动了 OneDrive,它就会自作聪明地自作主张, 把我们好不容易装上去的旧版更新为无法启动的最新版。

关闭自动更新

一个 OneDrive,三个更新进程,我们索性把它们全关掉:

1
2
3
launchctl remove com.microsoft.OneDriveStandaloneUpdater
sudo launchctl remove com.microsoft.OneDriveStandaloneUpdaterDaemon
sudo launchctl remove com.microsoft.OneDriveUpdaterDaemon

一不做二不休,配置文件也删了好了:

1
2
3
sudo rm /Library/LaunchAgents/com.microsoft.OneDriveStandaloneUpdater.plist
sudo rm /Library/LaunchDaemons/com.microsoft.OneDriveStandaloneUpdaterDaemon.plist
sudo rm /Library/LaunchDaemons/com.microsoft.OneDriveUpdaterDaemon.plist

这时我们再启动 OneDrive,整个世界清净了。

总结

如果你还在使用 catalina,并且也在使用 OneDrive 时,那么大概率会遇到这个问题, 其实我理解微软的做法,淘汰某个古老系统是正常的,更新也是按正常逻辑运行的, 但是两个加在一起,老系统就不正常了,这不能作为让用户承担问题的理由, 起码应该下发一个版本,停止旧版本的自动更新,再接下来向新系统下发更新。 不过我的牢骚微软应该是听不到的,如果你也有这个问题,那么请按以下步骤尝试:

  1. 重装旧版本应用,重装后不要立即启动
  2. 停用所有更新相关服务
l

1. 怎样锻炼口才?16种口才训练方法

看进去的东西,不是口才;讲出来的,才是口才;吸收进去的,不是口才,表达出来的才是口才。拳不离手,曲不离口,学习口才也要不断地开口开口再口!

1、朗读朗诵

自己读书,大声地读出来。每天坚持朗读一些文章,既练习口齿清晰伶俐,又积累一些知识量信息量,更重要的是对身体大有裨益,清喉扩胸,纳天地之气,成浩然之身!大家多读一些积极向上的文章,特别是《世界上最伟大的推销员》,我们强烈建议大家能够读熟背透。每天坚持朗读半小时以上,坚持两年三年。

2、对着镜子训练

建议你在自己的起居室中或是办公室某一墙面安装一大镜子,每天在朗读过程中,去对着镜子训练,训练自己的眼神,训练自己的表情,训练自己的肢体语言,这样效果更好。

3、自我录音摄像

如果条件允许,我建议您每隔一周时间,把自己的声音和演讲过程拍摄下来,这样反复观摩,反复研究哪儿我卡壳了,哪儿手势没到位,哪儿表情不自然,天长日久,你的口才自然进步神速。看一次自己的摄像比上台十次二十次效果都好。

4、尝试躺下来朗读

如果你想练就一流的运气技巧,一流的共鸣技巧,我教你一个非常简单的方法,就是:躺下来大声读书!当我们躺下来时,必然就是腹式呼吸,而腹式呼吸是最好的练声练气方法。每天睡觉之前,躺在床上大声地朗读十分钟,每天醒来之前,先躺在床上唱一段歌,再起来。坚持一至两个月,你会觉得自己呼吸流畅了,声音洪亮了,音质动听了,更有穿透力了,更有磁性了!

5、速读训练

这种训练目的,是在于锻炼人口齿伶俐,语音准确,吐字清晰。 方法:找来一篇演讲辞或一篇文辞优美的散文。先拿来字典、词典把文章中不认识或弄不懂的字、词查出来,搞清楚,弄明白,然后开始朗读。一般开始朗读的时候速度较慢,逐次加快,一次比一次读得快,最后达到你所能达到的最快速度。读的过程中不要有停顿,发音要准确,吐字要清晰,要尽量达到发声完整。因为如果你不把每个字音都完整地发出来,那么,如果速度加快以后,就会让人听不清楚你在说些什么,快也就失去了快的意义。我们的快必须建立在吐字清楚、发音干净利落的基础上。我们都听过体育节目的解说专家宋世雄的解说,他的解说就很有“快”的功夫。宋世雄解说的“快”,是快而不乱,每个字,每个音都发得十分清楚、准确,没有含混不清的地方。我们希望达到的快也就是他的那种快,吐字清晰,发音准确,而不是为了快而快。

6、即兴朗读

平时空闲时,你可以随便拿一张报纸,任意翻到一段,然后尽量一气呵成的读下去。而且,在朗读过程中,能够注意一下,上半句看稿子,下半句离开稿子看前面(假设前面有听众)。长期以往,你发现自己记忆力加强许多,快速理解力和即兴构思能力也在加强。

7、背诵法

背诵,并不仅仅要求你把某篇演讲辞、散文背下来就算完成了任务,我们要求的背诵,一是要“背”,二还要求“诵”。这种训练的目的有两个:一是培养记忆能力,二是培养口头表达能力。尝试去背诵一些文章,一篇一篇地去完成。天长日久,那些文章字句自然就转化为自己的词语了,练到一定时间就能张口就来口出华章。所谓:熟读唐诗三百首,不会吟诗也会吟!

8、复述法

复述法简单地说,就是把别人的话重复地叙述一遍。可以找一位伙伴一起训练。首先,请对方随便讲一个话题,或是一个故事。自己先注意倾听。然后再向对方复述一遍。这种练习在于锻炼语言的连贯性及现场即兴构思能力,和语言组织能力。如果能面对众人复述就更好了,它还可以锻炼你的胆量,克服紧张心理。

9、模仿法

我们每个人从小就会模仿,模仿大人做事,模仿大人说话。其实模仿的过程也是一个学习的过程。我们小时候学说话是向爸爸、妈妈及周围的人学习,向周围的人模仿。那么我们练口才也可以利用模仿法,向这方面有专长的人模仿。这样天长日久,我们的口语表达能力就能得到提高。 ①模仿专人。在生活中找一位口语表达能力强的人,请他讲几段最精彩的话,录下来,供你进行模仿。你也可以把你喜欢的、又适合你模仿的播音员、演员、相声表演家等的声音录下来,然后进行模仿。 ②专题模仿。几个好朋友在一起,请一个人先讲一段小故事、小幽默,然后大家轮流模仿,看谁模仿的最像。为了刺激积极性,也可以采用打分的形式,大家一起来评分,表扬模仿最成功的一位。这个方法简单易行,且有娱乐性。所要注意的是,每个人讲的小故事、小幽默,一定要新鲜有趣,大家爱听爱学。而且在讲之前一定要进行一些准备,一定要讲得准确、生动、形象,千万不要把一些错误的东西带去,否则模仿的人跟着错了,害人害己。 ③随时模仿。我们每天都听广播,看电视、电影,那么你就可以随时跟着播音员、演员进行模仿,注意他的声音、语调,他的神态、动作,边听边模仿,边看边模仿,天长日久,你的口语能力就得到了提高。而且会增加你的词汇,增长你的文学知识。 ④要求要尽量模仿得像。要从模仿对象的语气、语速、表情、动作等多方面进行模仿,并在模仿中有创造,力争在模仿中超过对方。在进行这种练习时,一要注意选择适合自己的对象进行模仿。要选择那些对自己身心有好处的语言动作进行模仿,我们有些同学模仿力很强,可是在模仿时都不够严肃认真,专拣一些脏话进行模仿,久而久之,就形成了一种低级的趣味,我们反对这种模仿方法。

10、描述法

小的时候我们都学过看图说话,描述法就类似于这种看图说话,只是我们要看的不仅仅是书本上的图,还有生活中的一些景、事、物、人,而且要求也比看图说话高一些。简单地说,描述法也就是把你看到的景、事、物、人用描述性的语言表达出来。描述法可以说是比以上的几种训练法更进一步。这里没有现成的演讲辞、散文、诗歌等做你的练习材料,而要求你自己去组织语言进行描述。所以描述法训练的主要目的就在于训练同学们的语言组织能力和语言的条理性。在描述时,要能够抓住特点进行描述。语言要清楚,明白,要有一定的文采。一定要用描述性的语言,尽量生动些,活泼些。这可以训练我们积累优美词语的应用能力。

11、角色扮演法

在我们的培训过程中,经常让学员进行角色扮演,组织角色语言去演讲,叫“情境模拟训练法”,比如扮演律师,扮演市长答记者问,扮演领导开动员会,扮演新郎新娘即兴发言等等,还可以选择小品中的角色扮演,直接让学员去演小品,去扮演作品中出现的不同的人物,当然这个扮演主要是在语言上的扮演。这种训练的目的,在于培养人的语言的适应性、个性,以及适当的表情、动作。

12、讲故事法

我们的口才培训,要求学员能够讲100个以上的故事,不同时候要能够讲不同的故事,而且现场就能想出符合场合的故事。这就要求我们积累大量的素材。同时还要讲得动听,讲得精彩,熟能生巧,讲多了口才就来了!

13、积累知识,多翻翻字典、成语词典

建议各位办公桌上和家里都放一本《新华字典》和《现代汉语成语词典》,有空就翻翻,不认识的字多看看,认识的字也再看细些,你会发现中国的文字博大精深,坚持下去,你的词汇量会越来越多,你的口才自然越来越棒!

14、对口才产生兴趣 兴趣是最好的老师

当你的焦点在口才训练上时,你必然就会关注平时生活工作中的口才技巧。兴趣在哪里,焦点到哪里;焦点到哪里,学问到哪里!这种方法进步更快。即使看电视,也在注意台词的优美,交际的仪态,幽默的笑眼,必然进步神快!

15、写日记

写日记是最好的自我沟通的方法,每天写上一千来字,既整理自己的思路,反省当日之进步与不足,梳理自己的情绪,释放一些不快,又可以学会遣词造句,天长日久,手能写之,口必能言之。

16、多找机会上台

很多同学认为生活中缺少锻炼的舞台,没有公众场合发言的机会。其实,这是一种误区,我们平时生活工作中,公众演讲的机会太多了,只是我们没有发现,没有这个意识去参与。 如果你想突破口才瓶颈,你一定要多找机会讲话。每次开会,必定坐第一排,必定要举手发表一下自己的观点;有机会就给自己的员工、小组成员开开会;有机会还可以开开家庭会议,把家庭打造在学习口才的舞台;现在每逢节假日,路演比较流行,那一有机会你就上台去参与,不要管那么多,你只是在锻炼自己而已!放下自己,放下一切时,你发现学习口才就这么简单!

l

2019 Mac13.6安装cuda+cudnn+pytorch

本次主要记录一些自己遇到的问题的解决方案

CUDA、cuDNN

大致流程参照 2018 MAC安装CUDA、cuDNN(Gaming Box1070)

顺序是:GPU Driver、CUDA Driver、CUDA Toolkit、cuDNN

安装驱动时要注意:

1
2
MacOS与NVIDIA GPU Driver的版本要匹配,才能驱动显卡
CUDA Driver与NVIDIA GPU Driver的版本要一致,CUDA才能找到显卡

关键地址:

我自己安装好后的配置是:

  • GPU Driver Version: 387.10.10.10.40.105

  • CUDA Driver Version: 418.105

  • CUDA:10.1.105

  • cuDNN:7.4.

版本查看方式:

1
2
cat $CUDA_HOME/version.txt # CUDA
cat $CUDA_HOME/include/cudnn.h | grep CUDNN_MAJOR -A 2 #cuDNN

Pytorch with CUDA

Pytorch官方 说的很清楚:

1
MacOS Binaries dont support CUDA, install from source if CUDA is needed

目前要with CUDA有两个方式:从源码安装、安装第三方pip包

从源码安装

参照:https://github.com/pytorch/pytorch#from-source

这条路我没走通。虽然成功编译了pytorch,但执行import torch时遇到了错误:

1
ModuleNotFoundError: No module named 'torch._C'

按照google结果,我应该在 torch 目录的以下两个文件复制:

1
2
cp _C.cpython-37m-darwin.so _C.so
cp _dl.cpython-37m-darwin.so _dl.so

但我的 torch 目录下没有这两个文件

第三方pip包

参照:https://github.com/TomHeaven/pytorch-osx-build

我下载的是:pytorch-1.0-py27-py37-cuda10-cudnn74

这是别人编译好的pip包,下载后用pip安装即可:

1
pip install torch-1.0-cp37-cp37m-macosx_10_13_x86_64.whl

No module named ‘torch._C’
Library not loaded: @rpath/xxxx.10.0.dylib
在运行 import torch 时报的错,比如:

1
2
3
4
5
6
7
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/jerry/anaconda3/lib/python3.7/site-packages/torch/__init__.py", line 84, in <module>
from torch._C import *
ImportError: dlopen(/Users/jerry/anaconda3/lib/python3.7/site-packages/torch/_C.cpython-37m-darwin.so, 9): Library not loaded: @rpath/libcufft.10.0.dylib
Referenced from: /Users/jerry/anaconda3/lib/python3.7/site-packages/torch/lib/libtorch_python.dylib
Reason: image not found

libcufft是cuda的库,这里只是文件名没找到而已,而库文件其实是在 $CUDA_HOME/lib下的,所以手动链接一下就行,下面是对同类错误的库进行统一处理:

1
2
3
4
5
6
sudo ln -s $CUDA_HOME/lib/libcufft.10.dylib $CUDA_HOME/lib/libcufft.10.0.dylib
sudo ln -s $CUDA_HOME/lib/libcurand.10.dylib $CUDA_HOME/lib/libcurand.10.0.dylib
sudo ln -s $CUDA_HOME/lib/libcublas.10.dylib $CUDA_HOME/lib/libcublas.10.0.dylib
sudo ln -s $CUDA_HOME/lib/libcudart.dylib $CUDA_HOME/lib/libcudart.10.0.dylib
sudo ln -s $CUDA_HOME/lib/libnvrtc.dylib $CUDA_HOME/lib/libnvrtc.10.0.dylib
sudo ln -s $CUDA_HOME/lib/libcusparse.10.dylib $CUDA_HOME/lib/libcusparse.10.0.dylib

在import torch的时候报错

1
2
3
4
5
6
7
8
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.7/site-packages/torch/__init__.py", line 84, in <module>
from torch._C import *
ImportError: dlopen(/usr/local/lib/python3.7/site-packages/torch/_C.cpython-37m-darwin.so, 9): Library not loaded: @rpath/libcufft.10.0.dylib
Referenced from: /usr/local/lib/python3.7/site-packages/torch/lib/libtorch_python.dylib
Reason: image not found

尝试了以上办法,还是import不了。也Google找了一下其他办法,有人说是brew install libomp一下就可以解决了,我这边是没有效果。

后来看到Tomheaven提供的pip包是需要cuda和cuDNN一一对应的,一开始没发现。

比如说如果要装pytorch1.4的话,cuda和cuDNN的版本就必须是10和7.4。因此我卸载了之前装的cuda和cuDNN重装了对应版本的。

再来安装pytorch时,还是有个问题

1
2
$ pip install /Users/shirleytse/Downloads/torch-1.0-cp37-cp37m-macosx_10_13_x86_64.whl 
ERROR: torch-1.0-cp37-cp37m-macosx_10_13_x86_64.whl is not a supported wheel on this platform.

把pip改成pip3就可以了。

检查pytorch是否安装成功

img

l

Mac python 多版本安装、删除、切换

一、安装pyenv

1
brew install pyenv

img

二、查看当前安装的pyenv的版本

1
pyenv -v

img

三、将pyenv配置到全局环境变量中

1.打开全局的环境变量配置文件

1
vim  /etc/profile 

2.在profile 文件最下边加上这两行配置

1
2
export PYENV_ROOT=~/.pyenv
export PATH=$PYENV_ROOT/shims:$PATH

img

3.使环境变量配置文件立即生效

1
source /etc/profile 

img

四、查看所有的python版本

1
pyenv versions 

img

*指向的是当前所使用的版本,system是系统安装的python

五、查看所有可以安装的python版本

1
pyenv install --list

img

列表很长,这里截图只是一部分。

六、安装指定版本的python

命令格式:pyenv install 版本号 ,eg:

1
2
pyenv install 3.8.9
pyenv rehash # 在进行安装、删除指定python版本后使用,更新版本管理数据库

img

七、查看当前安装的所有版本

1
pyenv versions

八、切换python版本

  1. 全局切换

命令格式:pyenv global 版本号,eg:

1
pyenv global 3.5.5
  1. 当前目录及其子目录生效(激活)

命令格式: pyenv local 版本号, eg:

1
pyenv local 3.5.5

激活后,在每次进入该目录时会自动切换到指定的版本。如果取消激活则使用–unset参数:

1
pyenv local --unset
  1. 验证是否切换成功
1
python -V 

九、卸载指定的Python版本

1
2
pyenv uninstall 3.8.9
pyenv rehash # 在进行安装、删除指定python版本后使用,更新版本管理数据库

十、更新版本管理数据库

在进行安装、删除指定python版本后使用,更新版本管理数据库

1
pyenv rehash 

注意:

1
2
3
4
5
export PYENV_ROOT=~/.pyenv
export PATH=$PYENV_ROOT/shims:$PATH 为什么我在/etc/profile 加了这两行不生效,
然后在 ~/.zshrc 加了下面内容就好了
export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init --path)"
l