文件I-O优化技巧

一般来说,所有工程都会有对文件进行读写的操作。如果你不仅是要存储少量字节(例如JSON文件),就有必要考虑性能问题了。因为这种情况下很容易写出低效率的文件读写代码,而且编译器和Unity都无法帮助你优化。今天这篇文章将分享文件读写代码中一些常见的误区,希望对大家有所帮助。

.NET
API提供了很多完善的写入文件系统相关的类。Stream抽象类和其子类FileStream,还有File类,带有静态函数Open以及很方便的读写函数如BinaryReader和BinaryWriter。C#语言本身提供了using语法可以方便地关闭文件流、文件读写对象实例和文件句柄。这类代码使用便利,安全系数高,容易实现:

基本来说,文件操作可以归纳为以下五个步骤:

打开文件:File.Open

读取字节:stream.Read

写入字节:stream.Write

查询位置: stream.Position 或 stream.Seek

关闭文件:stream.Dispose 或 stream.Close

如果反编译FileStream类,你会发现stream.Position和stream.Seek其实没有什么区别,仅仅是API叫法上的不同。还有,stream.Dispose和stream.Close也基本上没什么区别,都能关掉文件。

得益于streams,readers,writers的便利性,想要测试它们的性能也很方便。进行读写操作只需调用一个函数即可,但这些读写操作函数的性能消耗可大不相同。接下来我针对这些不同的读写方式写了一个测试程序,下面是程序将要做的工作:

写入20MB,每次写入4KB的数据块

写入20MB,每次写入1字节

读取20MB,每次读取4KB的数据块

读取20MB,每次读取1字节

数次查找流的某个位置,次数与数据块的读写次数相同

数次打开某个文件,次数与数据块的读写次数相同

测试脚本如下:

新建Unity工程,在Assets目录下新建TestScript.cs脚本并复制以上代码。然后在默认的空场景中将TestScript附加到Camera游戏对象上,最后编译。注意编译平台使用64位,在非Development模式下编译,画质设置为最快,分辨率设为最低(640x480)。测试环境如下:

2.3 Ghz Intel Core i7-3615QM

Mac OS X 10.11.2

Apple SSD SM256E, HFS+ format

Unity 5.3.0f4, Mac OS X Standalone, x86_64, non-development

640×480, Fastest, Windowed

测试结果如下:

真是有着天壤之别,因此我单独提取出了最快的三个数据,得到了第二张图表。

可以看出,以数据块的方式进行读写操作的效率非常之高。虽然你可能不会一个一个字节地去写,但却有可能以4字节(整数)或类似大小为数据块单位进行写入操作。所以尽可能以较大的数据块为单位进行操作,这将提高38倍的写入效率,205倍的读取效率!

流查找(不论设置Position或调用Seek)不会进行字节读写,但这种操作是有代价的,虽然没有其它类型的操作那样多,它需要相当于读取4KB数据块三分之一的资源。所以最好避免这种查找操作,尽量线性读写文件,这样才能在各层面最大限度地发挥缓存的优势。

最后,打开或关闭文件同样不需要读取任何字节,但需要的时间会很长。事实证明,非常之长!打开和关闭文件需要的时间是以字节块写入整个文件所需时间的6.5倍,是字节块方式读取整个文件所需时间的40倍。考虑到读写操作的重要性,以及打开和关闭文件是操作系统的唯一需求,所以,除非特别需要,不要打开关闭文件。而且操作大文件要比操作多个小文件更好。

以上包括了关于I/O性能的一些建议,如有疑问,欢迎来下方评论区留言。

本文来源于:jacksondunstan.com

原作者:Jackson Dunstan

C#字符串连接的效率问题

C#字符串连接常用的四种方式:StringBuilder、+、string.Format、List

1.+的方式

1
2
string sql = "update tableName set int1=" + int1.ToString() + ",int2=" +
int2.ToString() + ",int3=" + int3.ToString() + " where id=" + id.ToString();

编译器会优化为:

1
2
3
string sql = string.Concat(new string[] { "update tableName set int1=",
int1.ToString(), ",int2=", int2.ToString(), ",int3=", int3.ToString(), " where
id=", id.ToString() });

下面是string.Concat的实现:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
public static string Concat(params string[] values)

{

int totalLength = 0;



if (values == null)



{



throw new ArgumentNullException("values");



}



string[] strArray = new string[values.Length];



for (int i = 0; i < values.Length; i++)



{



string str = values[i];



strArray[i] = (str == null) ? Empty : str;



totalLength += strArray[i].Length;



if (totalLength < 0)



{



throw new OutOfMemoryException();



}



}



return ConcatArray(strArray, totalLength);



}



private static string ConcatArray(string[] values, int totalLength)



{



string dest = FastAllocateString(totalLength);



int destPos = 0;



for (int i = 0; i < values.Length; i++)



{



FillStringChecked(dest, destPos, values[i]);



destPos += values[i].Length;



}



return dest;



}



private static unsafe void FillStringChecked(string dest, int destPos,
string src)



{



int length = src.Length;



if (length(dest.Length - destPos))



{



throw new IndexOutOfRangeException();



}



fixed (char* chRef = &dest.m_firstChar)



{



fixed (char* chRef2 = &src.m_firstChar)



{



wstrcpy(chRef + destPos, chRef2, length);



}



}



}

先计算目标字符串的长度,然后申请相应的空间,最后逐一复制,时间复杂度为o(n),常数为1。固定数量的字符串连接效率最高的是+。但是字符串的连+不要拆成多条语句,比如:

1
2
3
string sql = "update tableName set int1=";
sql += int1.ToString();
sql += ...

这样的代码,不会被优化为string.Concat,就变成了性能杀手,因为第i个字符串需要复制n-i次,时间复杂度就成了o(n^2)。

2.StringBuilder的方式

如果字符串的数量不固定,就用StringBuilder,一般情况下它使用2n的空间来保证o(n)的整体时间复杂度,常数项接近于2。

因为这个算法的实用与高效,.net类库里面有很多动态集合都采用这种牺牲空间换取时间的方式,一般来说效果还是不错的。

3.string.Format的方式

它的底层是StringBuilder,所以其效率与StringBuiler相似。

# 4.List它可以转换为string[]后使用string.Concat或string.Join,很多时候效率比StringBuiler更高效。

List与StringBuilder采用的是同样的动态集合算法,时间复杂度也是O(n),与StringBuilder不同的是:List的n是字符串的数量,复制的是字符串的引用;StringBuilder的n是字符串的长度,复制的数据。不同的特性决定的它们各自的适应环境,当子串比较大时建议使用List,因为复制引用比复制数据划算。而当子串比较小,比如平均长度小于8,特别是一个一个的字符,建议使用StringBuilder。

总结:

1>固定数量的字符串连接+的效率是最高的;

2>当字符串的数量不固定,并且子串的长度小于8,用StringBuiler的效率高些。

3>当字符串的数量不固定,并且子串的长度大于8,用List的效率高些。

把代码混淆过的游戏反混淆回来

最近一直在找如何在MAC上混淆Android的DLL,至今没能找到合适的,有大神知道记得告诉我喔。今天群里有人说了一个混淆代码和返混淆代码的工具de4dot
,不查不知道一查吓一跳。这玩意可以把别人混淆过的代码反混淆回来。

这个工程是开源的https://github.com/0xd4d/de4dot 找一台Windows电脑,下载后在VS上面打开它,另外它不能直接编译,需要在引入一个库文件。为了方便大家我把能正常编译的工程提供出来。

下载地址:http://pan.baidu.com/s/1ntjw3mL

如下图所示,打开工程后在VS里面快捷键F6,编译完成后会生成出来de4dot.exe
。那么我们返混淆就是通过这个exe来将unity生成的dll返混淆回来。

经过混淆的代码反编译后。

反混淆以后,如下图所示,属性名子就出来了。但是方法名就回不来了,都是按数字一次加上去,不过这些总比方括号好看多了。而且我看了一下内容,有些方法内容奇葩的地方也都解的至少能看。

在Windows里面的cmd里面输入

de4dot.exe test.dll -p un

另外你随便下载一个用Unity做的安卓游戏,然后把apk解开把里面的Assembly-
CSharp.dll找出来,它的代码都在里面。如果它的代码没有做混淆工作,那么可以直接反编译出来。 如下图所示,返混淆会在原始dll的目录下生成一个后缀-
cleaned.dll的文件。然后用反编译工具打开它即可。

de4dot
看起来应该是非常强大,他支持返混淆这么多工具。。我就不一个一个试了,希望阅读本文的你帮我做一下测试,看看是不是所有混效果的Unity游戏都可以解开。

Agile.NET (aka CliSecure)

Babel.NET

CodeFort

CodeVeil

CodeWall

CryptoObfuscator

DeepSea Obfuscator

Dotfuscator

.NET Reactor

Eazfuscator.NET

Goliath.NET

ILProtector

MaxtoCode

MPRESS

Rummage

Skater.NET

SmartAssembly

Spices.Net

Xenocode

de4dot不仅仅可以返混淆,它也可以自身去混淆。。大家可以看看它的gitHub上的介绍,哎 又是一个必须在Windwos上完成的操作。。
今天把研究成果发出来,希望大家多多测试一下,看看能不能把所有混淆过的Unity游戏返混淆回来。 另外有什么最近进展大家一定要告诉我呀。。嘿嘿嘿嘿。

雨松MOMO提醒您:亲,如果您觉得本文不错,快快将这篇文章分享出去吧 。另外请点击网站顶部彩色广告或者捐赠支持本站发展,谢谢!

作者:雨松MOMO

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×