Neo's Blog

不抽象就无法深入思考
不还原就看不到本来面目!

0%

0. 背景介绍,把人吸引过来

之前总有一些年轻人问我,我应该了解哪些知识才能像某某某那么牛B。
这句话的意思其实就是:他们特别困惑,想知道一个后端程序员的知识体系,想知道从哪开始学起。

关于这个问题,琢磨了好久,我不想简单的一句话就敷衍过去了,这个问题我要深思熟虑去回答。
因为如果 10 年前有人告诉我这个问题的答案,现在的我将少走很多的弯路,技术水平也会更上一层楼。

简单说一下全文的结构,全文一共分为四大部分。第一部分,主要从硬件、操作系统、网络、数据结构&算法等几个方面跟大家聊一下计算机科学相关的基础知识。第二部分,讲一下设计一款高性能的服务框架,应该从哪些方面着手;第三部分,讲一下平常工作中使用最频繁的知识-数据库、缓存以及一些相关的经典问题;最后第四部分,讲述的侧重点从第二部分的微观转到相对宏观的内容,跟大家聊一下分布式系统、大型架构设计等相关知识。

引用古人的一句话,来开始我们的征程!

“路漫漫其修远兮,吾将上下而求索!”

Read more »

0. 背景介绍,把人吸引过来

之前总有一些年轻人问我,我应该了解哪些知识才能像某某某那么牛B。
这句话的意思其实就是:他们特别困惑,想知道一个后端程序员的知识体系,想知道从哪开始学起。

关于这个问题,琢磨了好久,我不想简单的一句话就敷衍过去了,这个问题我要深思熟虑去回答。
因为如果 10 年前有人告诉我这个问题的答案,现在的我将少走很多的弯路,技术水平也会更上一层楼。

简单说一下全文的结构,全文一共分为四大部分。第一部分,主要从硬件、操作系统、网络、数据结构&算法等几个方面跟大家聊一下计算机科学相关的基础知识。第二部分,讲一下设计一款高性能的服务框架,应该从哪些方面着手;第三部分,讲一下平常工作中使用最频繁的知识-数据库、缓存以及一些相关的经典问题;最后第四部分,讲述的侧重点从第二部分的微观转到相对宏观的内容,跟大家聊一下分布式系统、大型架构设计等相关知识。

引用古人的一句话,来开始我们的征程!

“路漫漫其修远兮,吾将上下而求索!”

Read more »

0. WHY DPDK

为什么这么说?基于 OS 内核的数据传输有什么弊端?#
1、中断处理。当网络中大量数据包到来时,会产生频繁的硬件中断请求,这些硬件中断可以打断之前较低优先级的软中断或者系统调用的执行过程,如果这种打断频繁的话,将会产生较高的性能开销。

2、内存拷贝。正常情况下,一个网络数据包从网卡到应用程序需要经过如下的过程:数据从网卡通过 DMA 等方式传到内核开辟的缓冲区,然后从内核空间拷贝到用户态空间,在 Linux 内核协议栈中,这个耗时操作甚至占到了数据包整个处理流程的 57.1%。

3、上下文切换。频繁到达的硬件中断和软中断都可能随时抢占系统调用的运行,这会产生大量的上下文切换开销。另外,在基于多线程的服务器设计框架中,线程间的调度也会产生频繁的上下文切换开销,同样,锁竞争的耗能也是一个非常严重的问题。

4、局部性失效。如今主流的处理器都是多个核心的,这意味着一个数据包的处理可能跨多个 CPU 核心,比如一个数据包可能中断在 cpu0,内核态处理在 cpu1,用户态处理在 cpu2,这样跨多个核心,容易造成 CPU 缓存失效,造成局部性失效。如果是 NUMA 架构,更会造成跨 NUMA 访问内存,性能受到很大影响。

5、内存管理。传统服务器内存页为 4K,为了提高内存的访问速度,避免 cache miss,可以增加 cache 中映射表的条目,但这又会影响 CPU 的检索效率。

综合以上问题,可以看出内核本身就是一个非常大的瓶颈所在。那很明显解决方案就是想办法绕过内核。

Read more »

$Y = \theta * X + \epsilon $

我们现在的目标是找到一组$\theta$可以让它乘以X之后尽可能的匹配Y

$\epsilon$为误差,服从均值为0,方差为$\mu^2$的正态分布

预测值与真实值之间有误差

学习的关键在于:确定什么样的参数最符合于你的目标

我们的目标是什么? 让误差项尽可能的小,接近于0,loss函数等于0

条件概率: 也服从正态分布。

对于某一个样本,要找到一个theta, 跟x组合之后,成为真实值y的可能性最大

Read more »

sk learn的六大功能:

分类

回归

聚类

预处理

模型选择

降维

8:2
cross validation

Training 切成N份(例如10)

选前9份,进行交叉验证,对结果进行平均(准确率一般平均)。
Validation 验证数据

Testing 比较宝贵

confusion matrix 混淆矩阵,relative with 召回率,准确率

TruePostive FalsePostive
FalseNegative TrueNegative

True 做到了
Postive 正类

评价指标有几个:

recall 召回率

precision 准确率

F1 score (调和平均数) = 2 / (1 + precision) + 1 (1 + recall) 给予低值更高的权重

various thresholds(阈值就是要求,越高越严格!) 跟 score比较

predict = score > thresholds : True : False

随着thresholds从低到高,精确率上升,召回率降低;

准确率-召回率曲线

ROC curves (ROC曲线)

AUC 测量曲线下面积(综合评估),最好是1,最差是0.5

https://blog.csdn.net/fencecat/article/details/123530441

“好”的代码与“坏”的代码

虽然对于“什么是优秀的代码“难以形成一致意见,但是这么多年的经验,让我对代
码“好”与“坏”积累了一些自己的看法。

比如说,“好”的代码应该:

1.容易理解;
2.没有明显的安全问题:
3.能够满足最关键的需求;
4.有充分的注释;
5.使用规范的命名;
6.经过充分的测试。

“坏”的代码包括:
1.难以阅读的代码;
2.浪费大量计算机资源的代码;
3.代码风格混乱的代码;
4.复杂的、不直观的代码;
5.没有经过适当测试的代码。

image

image

这种阅读起来的确定性至少有三点好处,第一点是可以减少代码错误;第二点是可以节省我思考的时间;第三点是可以节省代码阅读者的时间。

减少错误、节省时间,是我们现在选择编码方式的一个最基本的原则。

两个考察点:分治、双指针实现快速partition

时间复杂度:

平均 lgn;
最坏:n^2
最好:

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
41
42
43
44
45
46
47
48
49
50
51
52
//快速排序算法模板
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;

int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}

下面这道题目是基于快排思想的TopK,他们的partion逻辑不太一样,其中一个很重要的点在于:
quickselect要求把输入分成了三部分,[<=X] X [>X];
而上面的quicksort只是把输入分成了两部分,[<X],[>=X]或者[<=X], [>X]
分割点在某一区间内,但是不一定在区间的两端;

```cpp
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
qselect(input, 0, input.size() - 1, k);
vector<int> res(k);
for (int i = 0; i < k; ++i) {
res[i] = input[i];
}
return res;
}

void qselect(vector<int>& q, int l, int r, int k) {
if (l >= r) return;

int i = l, j = r, x = q[l];
while (i < j) {
while(i < j && q[j] > x) j--; //优先从j开始
q[i] = q[j];
while (i < j && q[i] < x) i++; //注意<=,而不能是<
q[j] = q[i]
}
a[i] = x;

//qselect(q, l, j, k);
//qselect(q, j + 1, r, k);

if (i - l + 1 == k ) return;
else if (i - l + 1 < k) qselect(q, j + 1, r, k - (i - l + 1));
else qselect(q, l, j - 1, k);
}
};

对于有序数组,上面把start作为Pivot的方法,会让算法时间复杂度退化为O(N^2),导致超时。
对数组进行shuffle,能解决有序数组的问题,但是无法解决所有元素全部相同的问题。当所有元素都一样时,shuffle无用,时间复杂度还是O(N^2)
于是就有了下面的解法。这种方法,返回了中间区域的左右边界,让问题迅速简化!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

void quick_sort_3part(int q[], int l, int r) {
if (l >= r) return;

swap(q[l], q[l + rand() % (r - l + 1)]); //随机化
int lt = l, gt = r, i = l + 1, x = q[l];
// 6,4,1,3,6,6,6,6,4,3,10,45,32
// | | | |
// l lt i gt
// 三路快排,确定等于主元pivot的左右边界
while (i <= gt) {
if (q[i] < x) swap(q[i++], q[++lt]); //gt左边的元素都比x大,但是gt指向的不一定大
else if (q[i] > x) swap(q[i], q[gt--]);//把比x大的元素,换到后面了;但换回来的元素不一定“满足条件”,所以i不能变,需要再比较一次!
else i++;// 等于主元,则继续
}
swap(q[l], q[lt]);

quick_sort_3part(q, l, lt - 1);
quick_sort_3part(q, gt + 1, r);
}

一群孩子做游戏,现在请你根据游戏得分来发糖果,要求如下:

  1. 每个孩子不管得分多少,起码分到一个糖果。
  2. 任意两个相邻的孩子之间,得分较多的孩子必须拿多一些糖果。(若相同则无此限制)

给定一个数组 arrarr 代表得分数组,请返回最少需要多少糖果。

要求: 时间复杂度为 O(n)O(n) 空间复杂度为 O(n)O(n)

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

class Solution {
public:
/**
* pick candy
* @param arr int整型vector the array
* @return int整型
*/
int candy(vector<int>& arr) {
//正向遍历一次,满足了中间大于左边时的要求;
vector<int> s(arr.size(), 1);
for (int i = 1;i < arr.size(); i++){
if (arr[i] > arr[i - 1]) {
s[i] = s[i - 1] + 1;
}
}

//逆向遍历一遍,继续满足中间大于右边时的要求
for (int i = arr.size() - 2; i >= 0; i--) {
if (arr[i] > arr[i + 1] && s[i] <= s[i + 1]) {
s[i] = s[i + 1] + 1;
}
}

int res = 0;
for (auto x : s) {
res += x;
}

return res;
}
};

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
41
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/

class Solution {
public:
/**
*
* @param head ListNode类
* @return ListNode类
*/
ListNode* deleteDuplicates(ListNode* head) {
ListNode* pre = new ListNode(-100000);
ListNode* nHead = pre, *tail = pre;
int preCnt = 2;
while (head) {
if (head->val == pre->val) { //如果当前元素 与 前一个元素;
preCnt++;
} else {
if (preCnt == 1) { //如果前面的元素出现一次
tail->next = pre;
tail = pre;
tail->next = NULL;
}
pre = head;
preCnt = 1;
}
head = head->next;
}

if (preCnt == 1) {
tail->next = pre;
pre->next = NULL;
}

return nHead->next;
}
};