비트 연산과 nginx 성능 간의 연결
우리 모두는 nginx가 고성능으로 유명하다는 것을 알고 있는데, 이는 주로 nginx의 소스 코드 때문입니다. 이 기사에서는 비트 연산과 nginx 고성능 간의 연결에 대해 설명합니다.
(학습 동영상 공유: 프로그래밍 동영상)
비트 작업은 Nginx 소스 코드의 모든 곳에서 볼 수 있으며, 명령어 유형 정의(수행할 수 있는 매개변수 수, 구성 블록이 나타날 수 있음), 현재 요청이 아직 전송되지 않은 데이터가 있고 포인터의 가장 낮은 비트는 Nginx 이벤트 모듈에서 이벤트가 만료되었는지 여부를 표시하는 데 사용됩니다. 이는 모두 비트 작업의 마법과 매력을 반영합니다.
이 기사에서는 Nginx 소스 코드의 일부 고전적인 비트 연산을 소개 및 분석하고 다른 비트 연산 기술을 소개하도록 확장합니다.
Alignment
Nginx는 내부적으로 메모리를 할당할 때 메모리 시작 주소의 정렬, 즉 메모리 정렬(일부 성능 향상으로 이어질 수 있음)에 큰 주의를 기울입니다. 이는 프로세서의 주소 지정 특성과 관련이 있습니다. 특정 처리와 같은 프로세서는 4바이트 너비에 따라 주소를 지정합니다. 이러한 머신에서는 0x46b1e7이 4바이트 경계(0x46b1e7 % 4 = 3)에 있지 않기 때문에 0x46b1e7부터 시작하는 4바이트를 읽어야 한다고 가정합니다. )이므로 읽을 때 두 단계로 읽습니다. 처음에는 0x46b1e4부터 4바이트를 읽고 하위 3바이트를 꺼낸 다음 0x46b1e8부터 4바이트를 읽고 가장 높은 바이트를 꺼냅니다. 우리는 주 메모리 읽기 및 쓰기 속도가 CPU와 일치할 수 없다는 것을 알고 있으므로 두 번의 읽기는 분명히 더 큰 오버헤드를 가져와 명령 지연을 일으키고 CPI(명령당 사이클)를 증가시키며 애플리케이션 성능을 저하시킵니다.
그래서 Nginx는 정렬 작업을 위해 특별히 매크로를 캡슐화합니다.
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
위 코드에 표시된 것처럼 이 매크로는 d를 a로 정렬합니다. 여기서 a는 2의 거듭제곱이어야 합니다.
예를 들어, d가 17이고 a가 2이면 d가 15이고 a가 4이면 16이 되고, d가 16이고 a가 4이면 16이 됩니다.
이 매크로는 실제로 d보다 크거나 같은 첫 번째 a의 배수를 찾고 있습니다. a는 2의 거듭제곱이므로 a의 이진수 표현은 00...1...00 형식입니다. 즉, 1이 하나만 있으므로 a - 1은 00...01...1입니다. 형식을 사용하면 ~(a - 1)은 모든 하위 n 비트를 0으로 설정합니다. 여기서 n은 a의 하위 비트에 있는 연속 0의 수입니다. 그래서 이때 d와 ~(a - 1)을 비트별 AND 연산을 수행하게 하면 d의 하위 n 비트를 지울 수 있습니다. d보다 크거나 같은 수를 찾아야 하므로 d +를 사용합니다. (a-1).
Bitmap
비트맵은 일반적으로 사물의 상태를 표시하는 데 사용됩니다. "비트"는 각 사물이 단 하나의 비트로 표시된다는 사실에 반영되어 메모리를 절약하고 성능을 향상시킵니다.
Nginx에는 공유 메모리 할당자(슬랩)와 같이 비트맵을 사용하는 예가 많이 있으며, uri(Uniform Resource Identifier)를 이스케이프 처리할 때 문자가 예약된 문자인지 여부를 확인해야 합니다. ), 이러한 문자는 %XX로 이스케이프되어야 합니다.
static uint32_t uri_component[] = { 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ 0xfc009fff, /* 1111 1100 0000 0000 1001 1111 1111 1111 */ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ 0x78000001, /* 0111 1000 0000 0000 0000 0000 0000 0001 */ /* ~}| {zyx wvut srqp onml kjih gfed cba` */ 0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ };
위에 표시된 것처럼 간단한 배열은 총 8개의 숫자를 포함하는 비트맵을 형성합니다. 각 숫자는 32개의 상태를 나타내므로 이 비트맵에는 256개의 문자(확장 ASCII 코드 포함)가 포함됩니다. 비트 0은 이스케이프가 필요하지 않은 일반 문자를 나타내고, 비트 1은 이스케이프가 필요한 문자를 나타냅니다.
그럼 이 비트맵을 어떻게 사용하나요? Nginx는 uri를 순회할 때 간단한 문장을 통해 판단을 내립니다.
uri_component[ch >> 5] & (1U << (ch & 0x1f))
위에서 볼 수 있듯이 ch는 현재 문자를 나타내며, ch>> 5는 ch를 오른쪽으로 5비트 이동하여 32로 나누는 효과가 있습니다. 이 단계에서는 uri_comComponent에 있는 ch의 개수를 결정합니다. 오른쪽에서 (ch & 0x1f)는 ch의 하위 5비트를 가져옵니다. 이는 모듈로 32를 사용하는 것과 같습니다. 이 값은 ch의 어느 숫자가 해당 숫자(낮은 숫자에서 높은 숫자로 계산)에 있는지를 나타냅니다. 양쪽 값에 대해 비트별 AND 연산을 수행한 후 ch 문자가 위치한 비트맵 상태를 꺼냅니다. 예를 들어, ch는 '0'(즉, 숫자 48)이며, 이는 비트맵의 두 번째 숫자(48>5 = 1)에 존재하며, 이 숫자의 16번째 비트(0xfc009fff)에 있고, 따라서 상태는 0xfc009fff & 0x10000 = 0이므로 '0'은 범용 문자이므로 이스케이프할 필요가 없습니다.
위의 예에서 또 다른 비트 연산 기술을 볼 수 있습니다. 즉, 2의 거듭제곱인 숫자에 대해 모듈로 또는 나눗셈 연산을 수행할 때 비트 연산을 통해 구현할 수도 있는데, 이는 직접 연산보다 더 좋습니다. 나눗셈 및 모듈로 연산은 더 나은 성능을 제공하지만 올바른 최적화 수준을 사용하면 컴파일러가 이러한 최적화를 수행할 수도 있습니다.
최하위 비트 1의 위치 찾기
그럼 다른 응용 기술을 소개하겠습니다.
디지털 바이너리에서 가장 낮은 1의 위치를 찾으려면 비트 순회를 직관적으로 생각할 수 있습니다. 이 알고리즘의 시간 복잡도는 O(n)이며 성능이 만족스럽지 않습니다.
如果你曾经接触过树状数组,你可能就会对此有不同的看法,树状数组的一个核心概念是 计算 lowbit,即计算一个数字二进制里最低位 1 的幂次。它之所以有着不错的时间复杂度(O(logN)),便是因为能够在 O(1) 或者说常数的时间内得到答案。
int lowbit(int x) { return x & ~(x - 1); }
这个技巧事实上和上述对齐的方式类似,比如 x 是 00...111000 这样的数字,则 x - 1 就成了 00...110111,对之取反,则把原本 x 低位连续的 0 所在的位又重新置为了 0(而原本最低位 1 的位置还是为 1),我们会发现除了最低位 1 的那个位置,其他位置上的值和 x 都是相反的,因此两者进行按位与操作后,结果里只可能有一个 1,便是原本 x 最低位的 1。
寻找最高位 1 的位置
换一个问题,这次不是寻找最低位,而是寻找最高位的 1。
这个问题有着它实际的意义,比如在设计一个 best-fit 的内存池的时候,我们需要找到一个比用户期望的 size 大的第一个 2 的幂次。
同样地,你可能还是会先想到遍历。
事实上 Intel CPU 指令集有这么一条指令,就是用以计算一个数二进制里最高位 1 的位置。
size_t bsf(size_t input) { size_t pos; __asm__("bsfq %1, %0" : "=r" (pos) : "rm" (input)); return pos; }
这很好,但是这里我们还是期望用位运算找到这个 1 的位置。
size_t bsf(size_t input) { input |= input >> 1; input |= input >> 2; input |= input >> 4; input |= input >> 8; input |= input >> 16; input |= input >> 32; return input - (input >> 1); }
这便是我们所期望的计算方式了。我们来分析下这个计算的原理。
需要说明的是,如果你需要计算的值是 32 位的,则上面函数的最后一步 input |= input >> 32 是不需要的,具体执行多少次 input |= input >> m, 是由 input 的位长决定的,比如 8 位则进行 3 次,16 位进行 4 次,而 32 位进行 5 次。
为了更简洁地进行描述,我们用 8 位的数字进行分析,设一个数 A,它的二进制如下所示。
A[7] A[6] A[5] A[4] A[3] A[2] A[1] A[0]
上面的计算过程如下。
A[7] A[6] A[5] A[4] A[3] A[2] A[1] A[0] 0 A[7] A[6] A[5] A[4] A[3] A[2] A[1] --------------------------------------- A[7] A[7]|A[6] A[6]|A[5] A[5]|A[4] A[4]|A[3] A[3]|A[2] A[2]|A[1] A[1]|A[0] 0 0 A[7] A[7]|A[6] A[6]|A[5] A[5]|A[4] A[4]|A[3] A[3]|A[2] -------------------------------------------------------------------------- A[7] A[7]|A[6] A[7]|A[6]|A[5] A[7]|A[6]|A[5]|A[4] A[6]|A[5]|A[4]|A[3] A[5]|A[4]|A[3]|A[2] A[4]|A[3]|A[2]|A[1] A[3]|A[2]|A[1]|A[0] 0 0 0 0 A[7] A[7]|A[6] A[7]|A[6]|A[5] A[7]|A[6]|A[5]|A[4] --------------------------------------------------------------------------------------------------------------------------------- A[7] A[7]|A[6] A[7]|A[6]|A[5] A[7]|A[6]|A[5]|A[4] A[7]|A[6]|A[5]|A[4]|A[3] A[7]|A[6]|A[5]|A[4]|A[3]|A[2] A[7]|A[6]|A[5]|A[4]|A[3]|A[2]|A[1] A[7]|A[6]|A[5]|A[4]|A[3]|A[2]|A[1]|A[0]
我们可以看到,最终 A 的最高位是 A[7],次高位是 A[7]|A[6],第三位是 A[7]|A[6]|A[5],最低位 A[7]|A[6]|A[5]|A[4]|A[3]|A[2]|A[1]|A[0]
假设最高位的 1 是在第 m 位(从右向左算,最低位称为第 0 位),那么此时的低 m 位都是 1,其他的高位都是 0。也就是说,A 将会是 2 的某幂再减一,于是最后一步(input - (input >> 1))的用意也就非常明显了,即将除最高位以外的 1 全部置为 0,最后返回的便是原来的 input 里最高位 1 的对应幂了。
计算 1 的个数
如何计算一个数字二进制表示里有多少个 1 呢?
直觉上可能还是会想到遍历(遍历真是个好东西),让我们计算下复杂度,一个字节就是 O(8),4 个字节就是 O(32),而 8 字节就是 O(64)了。
如果这个计算会频繁地出现在你的程序里,当你在用 perf 这样的性能分析工具观察你的应用程序时,它或许就会得到你的关注,而你不得不去想办法进行优化。
事实上《深入理解计算机系统》这本书里就有一个这个问题,它要求计算一个无符号长整型数字二进制里 1 的个数,而且希望你使用最优的算法,最终这个算法的复杂度是 O(8)。
long fun_c(unsigned long x) { long val = 0; int i; for (i = 0; i < 8; i++) { val += x & 0x0101010101010101L; x >>= 1; } val += val >> 32; val += val >> 16; val += val >> 8; return val & 0xFF; }
这个算法在我的另外一篇文章里曾有过分析。
观察 0x0101010101010101 这个数,每 8 位只有最后一位是 1。那么 x 与之做按位与,会得到下面的结果:
设 A[i] 表示 x 二进制表示里第 i 位的值(0 或 1)。 第一次: A[0] + (A[8] << 8) + (A[16] << 16) + (A[24] << 24) + (A[32] << 32) + (A[40] << 40) + (A[48] << 48) + (A[56] << 56) 第二次: A[1] + (A[9] << 8) + (A[17] << 16) + (A[25] << 24) + (A[33] << 32) + (A[41] << 40) + (A[49] << 48) + (A[57] << 56) ...... 第八次: A[7] + (A[15] << 8) + (A[23] << 16) + (A[31] << 24) + (A[39] << 32) + (A[47] << 40) + (A[55] << 48) + (A[63] << 56) 相加后得到的值为: (A[63] + A[62] + A[61] + A[60] + A[59] + A[58] + A[57] + A[56]) << 56 + (A[55] + A[54] + A[53] + A[52] + A[51] + A[50] + A[49] + A[48]) << 48 + (A[47] + A[46] + A[45] + A[44] + A[43] + A[42] + A[41] + A[40]) << 40 + (A[39] + A[38] + A[37] + A[36] + A[35] + A[34] + A[33] + A[32]) << 32 + (A[31] + A[30] + A[29] + A[28] + A[27] + A[26] + A[25] + A[24]) << 24 + (A[23] + A[22] + A[21] + A[20] + A[19] + A[18] + A[17] + A[16]) << 16 + (A[15] + A[14] + A[13] + A[12] + A[11] + A[10] + A[9] + A[8]) << 8 + (A[7] + A[6] + A[5] + A[4] + A[3] + A[2] + A[1] + A[0])
之后的三个操作:
val += val >> 32; val += val >> 16; val += val >> 8;
每次将 val 折半然后相加。
第一次折半(val += val >> 32)后,得到的 val 的低 32 位:
(A[31] + A[30] + A[29] + A[28] + A[27] + A[26] + A[25] + A[24] + A[63] + A[62] + A[61] + A[60] + A[59] + A[58] + A[57] + A[56]) << 24 + (A[23] + A[22] + A[21] + A[20] + A[19] + A[18] + A[17] + A[16] + A[55] + A[54] + A[53] + A[52] + A[51] + A[50] + A[49] + A[48]) << 16 + (A[15] + A[14] + A[13] + A[12] + A[11] + A[10] + A[9] + A[8] + A[47] + A[46] + A[45] + A[44] + A[43] + A[42] + A[41] + A[40]) << 8 + (A[7] + A[6] + A[5] + A[4] + A[3] + A[2] + A[1] + A[0] + A[39] + A[38] + A[37] + A[36] + A[35] + A[34] + A[33] + A[32])
第二次折半(val += val >> 16)后,得到的 val 的低 16 位:
15] + A[14] + A[13] + A[12] + A[11] + A[10] + A[9] + A[8] + A[47] + A[46] + A[45] + A[44] + A[43] + A[42] + A[41] + A[40] + A[31] + A[30] + A[29] + A[28] + A[27] + A[26] + A[25] + A[24] + A[63] + A[62] + A[61] + A[60] + A[59] + A[58] + A[57] + A[56]) << 8 + (A[7] + A[6] + A[5] + A[4] + A[3] + A[2] + A[1] + A[0] + A[39] + A[38] + A[37] + A[36] + A[35] + A[34] + A[33] + A[32] + A[23] + A[22] + A[21] + A[20] + A[19] + A[18] + A[17] + A[16] + A[55] + A[54] + A[53] + A[52] + A[51] + A[50] + A[49] + A[48])
第三次折半(val += val >> 8)后,得到的 val 的低 8 位:
(A[7] + A[6] + A[5] + A[4] + A[3] + A[2] + A[1] + A[0] + A[39] + A[38] + A[37] + A[36] + A[35] + A[34] + A[33] + A[32] + A[23] + A[22] + A[21] + A[20] + A[19] + A[18] + A[17] + A[16] + A[55] + A[54] + A[53] + A[52] + A[51] + A[50] + A[49] + A[48] + A[15] + A[14] + A[13] + A[12] + A[11] + A[10] + A[9] + A[8] + A[47] + A[46] + A[45] + A[44] + A[43] + A[42] + A[41] + A[40] + A[31] + A[30] + A[29] + A[28] + A[27] + A[26] + A[25] + A[24] + A[63] + A[62] + A[61] + A[60] + A[59] + A[58] + A[57] + A[56])
可以看到,经过三次折半,64 个位的值全部累加到低 8 位,最后取出低 8 位的值,就是 x 这个数字二进制里 1 的数目了,这个问题在数学上称为“计算汉明重量”。
位运算以它独特的优点(简洁、性能棒)吸引着程序员,比如 LuaJIT 内置了 bit 这个模块,允许程序员在 Lua 程序里使用位运算。学会使用位运算对程序员来说也是一种进步,值得我们一直去研究。
相关推荐:nginx教程
위 내용은 비트 연산과 nginx 성능 간의 연결의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











클라우드 서버에서 nginx 도메인 이름을 구성하는 방법 : 클라우드 서버의 공개 IP 주소를 가리키는 레코드를 만듭니다. Nginx 구성 파일에 가상 호스트 블록을 추가하여 청취 포트, 도메인 이름 및 웹 사이트 루트 디렉토리를 지정합니다. Nginx를 다시 시작하여 변경 사항을 적용하십시오. 도메인 이름 테스트 구성에 액세스하십시오. 기타 참고 : HTTPS를 활성화하려면 SSL 인증서를 설치하고 방화벽에서 포트 80 트래픽을 허용하고 DNS 해상도가 적용되기를 기다립니다.

nginx가 시작되었는지 확인하는 방법 : 1. 명령 줄을 사용하십시오 : SystemCTL 상태 nginx (linux/unix), netstat -ano | Findstr 80 (Windows); 2. 포트 80이 열려 있는지 확인하십시오. 3. 시스템 로그에서 nginx 시작 메시지를 확인하십시오. 4. Nagios, Zabbix 및 Icinga와 같은 타사 도구를 사용하십시오.

Docker 이미지 생성 단계 : 빌드 지침이 포함 된 Dockerfile을 작성하십시오. Docker 빌드 명령을 사용하여 터미널에 이미지를 빌드하십시오. Docker 태그 명령을 사용하여 이미지를 태그하고 이름과 태그를 지정하십시오.

nginx 버전을 쿼리 할 수있는 메소드는 다음과 같습니다. nginx -v 명령을 사용하십시오. nginx.conf 파일에서 버전 지시문을 봅니다. nginx 오류 페이지를 열고 페이지 제목을 봅니다.

Nginx 서버를 시작하려면 다른 운영 체제에 따라 다른 단계가 필요합니다. Linux/Unix System : Nginx 패키지 설치 (예 : APT-Get 또는 Yum 사용). SystemCTL을 사용하여 nginx 서비스를 시작하십시오 (예 : Sudo SystemCtl start nginx). Windows 시스템 : Windows 바이너리 파일을 다운로드하여 설치합니다. nginx.exe 실행 파일을 사용하여 nginx를 시작하십시오 (예 : nginx.exe -c conf \ nginx.conf). 어떤 운영 체제를 사용하든 서버 IP에 액세스 할 수 있습니다.

Linux에서는 다음 명령을 사용하여 nginx가 시작되었는지 확인하십시오. SystemCTL 상태 Nginx 판사 명령 출력에 따라 : "active : running"이 표시되면 Nginx가 시작됩니다. "Active : 비활성 (죽음)"이 표시되면 Nginx가 중지됩니다.

Linux에서 Nginx를 시작하는 단계 : Nginx가 설치되어 있는지 확인하십시오. systemctl start nginx를 사용하여 nginx 서비스를 시작하십시오. SystemCTL을 사용하여 NGINX를 사용하여 시스템 시작시 NGINX의 자동 시작을 활성화하십시오. SystemCTL 상태 nginx를 사용하여 시작이 성공했는지 확인하십시오. 기본 환영 페이지를 보려면 웹 브라우저의 http : // localhost를 방문하십시오.

Nginx 403 금지 된 오류를 수정하는 방법은 무엇입니까? 파일 또는 디렉토리 권한을 확인합니다. 2. 확인 파일을 확인하십시오. 3. nginx 구성 파일 확인; 4. nginx를 다시 시작하십시오. 다른 가능한 원인으로는 방화벽 규칙, Selinux 설정 또는 응용 프로그램 문제가 있습니다.
