第一章 简单动态字符串
2.1 引言
字符串String是程序设计中最为常见的一种数据结构,也是最为重要的一种数据结构,Hello World!这个最为精典的程序,是绝大部份人学习一种程序设计语言的入门程序,在这个最为精典的入门程序中,Hello World!就是字符串类型,字符串可以用于软件中信息的提醒、保存等,Redis中key都是String类型的,因此了解String类型对于我们了解Redis以及动手写Redis都是非常有益的。
2.2 字符串基本概念
以上是我们最为熟悉的Hello World!程序,这两段程序的输出结果都是一样的,数据结构上采用的也都是字符串,但是两者之间数据的存储区域以及数据的访问方式是完全不同的,程序2-1(a)与2-1(b)分别对应的字符串为常量字符串以及非常量字符串,那么两者之间有什么区别呢?
#include<stdio.h> int main() { char *hello = "hello world!"; printf("%s\n",hello); return 0; } (a) | #include<stdio.h> int main() { char hello[] = "hello world!"; printf("%s\n",hello); return 0; } (b) |
代码 2-1
2.2.1 常量/非常量
什么是常量字符串,由名字就可以看出,常量字符串就是字符串已经写死了,不容许外界对其进行修改,那么如何辩认字符串是不是常量字符串呢,常量字符串与非常量字符串一个重要区别就是在内存中存储的位置是不一样的,在程序中,将程序使用的空间大概划分成以下几个部份:
1、堆空间,在程序中堆空间一般由程序员进行分配与释放,其生命周期由程序设计人员进行控制。
2、栈空间,由编译器进行分配,用于存放程序中的局部变量、函数调用时的堆栈信息等,栈空间的大小受限于操作系统的软限制以及内存的硬限制。
3、静态存储区,该区域存储的是已初始化过的静态变量(包括局部静态变量以及全局静态态量)、初化过的全局变量以及常量字符串。
4、BSS内(未初始化的数据区),该区域主要用来存放全局未初始化变量,BSS区的数据在程序开始运行之前会被内核初始化为0或者空指针。
5、代码区,存放待执行的机器指令,该区域通常是只读存储区,避免由于对内存的误操作,导致代码运行的不正常。
下面通过一段简单的代码来说明C程序执行时的内存分配情况【代码2-2所示】,代码部分已经写了详细的注解:
#include<stdio.h> #include<stdlib.h> int a;//未初始化的全局变量存储在BSS区 static int t;//未初化的全局静态变量,存储在BSS区 int t = 0;//已初始化的全局变量,存储在静态存储区 static int m = 0;//已初始化的全局静态变量,存储在静态存储区 int main() { char c = 'a';//函数局部变量,存储在栈中 static int m; //未初化的局部静态变量,存储在BSS区 static int q = 0;//已初始化的局部静态变量,存储在静态存储区 char *addr = (char *) malloc(sizeof(char)*2);//在堆中分配2字节 char *p = "Hello World!";//Hello World存放在静态存储区中 } |
代码 2-2
操作系统为了节省内存,将程序中的常量字符串存储在静态存储区,程序运行时可以共享这些常量字符串,无需在开设空间用来存储这些常量字符串,我们将存储在静态存储区中的字符串称为常量字符串,将存储在堆栈中的字符串称为非常量是字符串,非常量字符串也就意味着在程序运行的过程中可以动态生成字符串,也可以对已有的字符串进行修改,下面的代码2-3显示了常量/非常量字符串的一个重要区别。
#include<stdio.h> int main() { char *ptr = "Hello World!"; *(ptr+1) = 'W'; printf("%s",ptr); } (a) | #include<stdio.h> int main() { char ptr[] = "Hello World!"; *(ptr+1) = 'W'; printf("%s",ptr); } (b) |
代码 2-3
在上述代码2-3(a)中,Hello World!是一个常量字符串,存储在静态存储区中,程序编译是不会报错的,但是程序运行时会出现运行时错误,如下图2-4所示,这是因为在程序的执行过程中我们试图通过指针修改静态内存区中的常量字符串,而静态内存区中的常量字符串是只读属性,操作系统不允许外界对其进行修改,因此程序执行时会报运行时错误。
图2-4 运行时错误
与代码2-3(a)相比,代码2-3(b)并没有多大的改变,惟一的改变就是将指针ptr改成了数组,但是这种改变,对编译器却造成了不同的影响,在代码2-3(a)中,编译器发现*ptr是一个指针,会在栈中开设4字节的变量用来存储ptr这个指针的变量,同时发现ptr这个指针变量已经初始化了,指向Hello World!这个字符串,编译器会将Hello World!这个字符串作为常量字符串存储在静态存储区中,并将其地址放在ptr这个指针变量中,代码2-3(b)中,编译器会发现ptr是一个数组,这时编译器的行为就是用Hello World!这个字符串来初始化ptr数组,ptr数组的长度编译器在初始化的过程中会计算出来,即为Hello World!字符串的长度13【备注:C语言中的字符串是以\0进行结尾的,因此实际长度要加上1】。
本文链接:自已动手写Redis【简单动态字符串序列一】,转载请注明。
由于公司想把部份业务迁到windowsazure,主要是应用winodwsazure的存储;在方案中为了体现存储的可靠性所以对winodwsazure存储进行了一系列的测试.但在读取压力测试环节中发现间歇性出现文件读取延时的情况,由于自己在编写测试应用方面比较善长(年长的农码),所以把问题归根于winodwsazure的存储上.经过和MS技术多次交流和帮助下才把问题明确下来,虽然问题不是程序代码产生,但和测试方法构建的测试数据有着关系.下面分享一下个测试过程.
目标
公司希望把网站存储的一些资源放到windowsazure上,总容量大概在3个T左右.出于方案实施严紧性考虑所以制定了一个压力测试计划,主要从读和写两方面来考察一下存储所支援的压力.写测试主要是分别写入:8k,16k,32k,64k,128k不同大小的文件而读取测试则随便获取写入的文件.
测试
在写入测试的时间还是非常顺利,存储节点的写入带宽基本可以达到3Gb,压力写入测试效果非常理想.但在压力读取的时候就出现异常,基本每隔2-3分钟就会出现读取延时,其延时时间竟然达到3-6秒.然后又恢复正常...
while (true)
{
stream.Position = 0;
TimeOutLog log = new TimeOutLog();
watch.Restart();
long index = System.Threading.Interlocked.Increment(ref mIndex);
string url = mImages[(int)(index % mImages.Count)];
CloudBlob blob = container.GetBlobReference(url);
blob.DownloadToStream(stream);
System.Threading.Interlocked.Increment(ref mCount);
}
由于测试代码非常简单,而测试机的CPU和内存都是比较充足,所以直接把问题指向了存储节点上.把问题汇总到winodwsazure方面的技术人员,经过对方调试排查后说程序构建测试的资源URL太多了可能是导致程序出现问题的主要原因.
在收到问题后我实在不理解,即使测试Url占用大量的内存也不可能影响程序运行,毕竟测试服务器的CPU根本没有压力.由于没有明确是否程序存在问题,所以我试图找方法来证明是存储的原因(毕竟公司采用的方案不能随便了结).通过修改程序把每个环节的运行时候记录下来.
TimeOutLog log = new TimeOutLog();watch.Restart();
long index = System.Threading.Interlocked.Increment(ref mIndex);
string url = mImages[(int)(index % mImages.Count)];
watch.Stop();
log.GetUrlTime = watch.Elapsed.TotalMilliseconds;
log.Url = url;
watch.Restart();
CloudBlob blob = container.GetBlobReference(url);
blob.DownloadToStream(stream);
watch.Stop();
log.GetBlobTime = watch.Elapsed.TotalMilliseconds;
if (log.GetBlobTime > 1000 || log.GetUrlTime > 1000)
mQueue.Enqueue(log);
System.Threading.Interlocked.Increment(ref mCount);
添加处理时间后明确发现是blob的downloadToStream存在间歇延时的情况,由于这个winodwsazure的API是由官方提供的.所以下了个定论程序不存在问题,应该是存储方面出现异常,然后邮件winodwsazure技术要求他们帮忙跟进一下.经过沟通对方建议把测试的url裁剪成N小分然后由开启多个程序进行压测,这个工作对我来说是比较方便调整,于是就把url拆分成N小份测试,结果没想到整个测试都非常顺利.
结总
.net程序占用大量内存后为什么影响downloadToStream导致延时的问题现在还没有清楚(毕竟不影响对存储的考察所以就没有再研究下去了),由于自己在编写.net程序有一定经验,所以开始坚信不是程序方面出的问题...刚开始挺排斥说是数据或程序原因导致的问题.后面拆成N个小文件的动机也是为了更进一步想证明存在问题.结果论证了我的想法是错误的,有时凭某方面的经验主观的判断一个事情的确是件很不好的方式.以下分享一下winodwsazure存储的测试结果
winodwsazure单个结点的存储读写还是非常给力的,基本按MS所说的一个帐号达到3Gb的读写流量.
补充:说句心理话MS的技术服务真的很倒位,虽然公司还没有正式购买服务,不过已体会到支持的到位(在这里谢谢技术支持的徐总)
本文链接:azure存储压测的问题(农码主观意识太强被坑了),转载请注明。
没有评论:
发表评论