一个C#和C++执行效率对比的简单实例
这里用一个算法题进行比较。
原题是见http://acm.hdu.edu.cn/showproblem.php?pid=4090,登载在http://blog.csdn.net/woshi250hua/article/details/7997550
作者提供了一个比较快的答案。
我之前也尝试做了一个,没有用递归,但也没有用作者使用的格局保存的剪枝方案,比较慢,随后看了作者的方案后再整合进了一个基本等效的格局保存逻辑。
以下是作者的C++程序的基本等价的C#程序,
1 using System.Collections.Generic; 2 3 namespace HDU4090 4 { 5 internal class SolveGemAndPrince2 6 { 7 private const int Max = 10; 8 9 private struct Node 10 { 11 public int X; 12 public int Y; 13 } 14 15 private readonly Node[] _qu = new Node[Max*Max]; 16 private readonly Dictionary<string, int> _hash = new Dictionary<string, int>(); 17 18 private readonly int[,] _dir = new[,] 19 { 20 {1, 0}, {1, 1}, {1, -1}, {-1, 0}, {-1, -1}, {-1, 1}, {0, 1}, {0, -1} 21 }; 22 23 private static void Change(int[,] mmap, ref int n, ref int m) 24 { 25 int i, j, tn = 0, tm = 0; 26 var k = new int[10]; 27 for (j = 0; j < m; ++j) 28 { 29 30 k[j] = 0; 31 for (i = 0; i < n; ++i) 32 if (mmap[i, j] != 0) mmap[k[j]++, j] = mmap[i, j]; 33 } 34 for (j = 0; j < m; ++j) 35 if (k[j] != 0) 36 { 37 38 for (i = 0; i < k[j]; ++i) 39 mmap[i, tm] = mmap[i, j]; 40 for (i = k[j]; i < n; ++i) 41 mmap[i, tm] = 0; 42 tm++; 43 if (k[j] > tn) tn = k[j]; 44 } 45 n = tn; 46 m = tm; 47 } 48 49 private int Ok(int[,] temp, int[,] vis, int i, int j, int n, int m) 50 { 51 var cnt = 0; 52 int head = 0, tail = 0; 53 Node cur; 54 55 cur.X = i; 56 cur.Y = j; 57 _qu[head++] = cur; 58 vis[cur.X,cur.Y] = 1; 59 60 while (tail < head) 61 { 62 cur = _qu[tail++]; 63 cnt++; 64 int k; 65 for (k = 0; k < 8; ++k) 66 { 67 Node next; 68 next.X = cur.X + _dir[k,0]; 69 next.Y = cur.Y + _dir[k,1]; 70 if (next.X >= 0 && next.X < n 71 && next.Y >= 0 && next.Y < m 72 && vis[next.X,next.Y] == 0 73 && temp[next.X,next.Y] == temp[i,j]) 74 { 75 76 _qu[head++] = next; 77 vis[next.X,next.Y] = 1; 78 temp[next.X,next.Y] = 0; 79 } 80 } 81 } 82 temp[i,j] = 0; 83 return cnt >= 3 ? cnt : 0; 84 } 85 86 static string GetHash(int[,] temp,int n,int m) 87 { 88 var s = ""; 89 for (var i = 0; i < n; ++i) 90 for (var j = 0; j < m; ++j) 91 s += temp[i,j] + '0'; 92 return s; 93 } 94 95 static void Mem(int[,] temp, int[,] mmap, int n, int m) 96 { 97 for (var i = 0; i < Max; i++ ) 98 for (var j = 0; j < Max; j++) 99 temp[i, j] = 0; 100 for (var i = 0; i < n; ++i) 101 for (var j = 0; j < m; ++j) 102 temp[i,j] = mmap[i,j]; 103 } 104 105 int Dfs(int[,] mmap,int n,int m) 106 { 107 108 if (n*m < 3) return 0; 109 var temp = new int[Max,Max]; 110 int i, j; 111 var vis = new int[Max,Max]; 112 var cnt = 0; 113 114 for (i = 0; i < n; ++i) 115 for (j = 0; j < m; ++j) 116 if (vis[i,j]==0 && mmap[i,j]!=0) 117 { 118 Mem(temp, mmap, n, m); 119 var ans = Ok(temp, vis, i, j, n, m); 120 if (ans >= 3) 121 { 122 ans = ans*ans; 123 int tn = n, tm = m; 124 Change(temp, ref tn, ref tm); 125 var s = GetHash(temp, tn, tm); 126 #if true 127 if (!_hash.ContainsKey(s)) 128 ans += Dfs(temp, tn, tm); 129 else ans += _hash[s]; 130 #else 131 ans += Dfs(temp, tn, tm); 132 #endif 133 if (ans > cnt) cnt = ans; 134 } 135 } 136 137 _hash[GetHash(mmap, n, m)] = cnt; 138 return cnt; 139 } 140 141 public int Solve(int n, int m, int k, int[,] gems) 142 { 143 _hash.Clear(); 144 return Dfs(gems, n, m); 145 } 146 } 147 }
再接下来是我的方案(纯粹凑热闹,没有参与这里的对比),
1 using System; 2 using System.Collections.Generic; 3 4 namespace HDU4090 5 { 6 public class SolveGemAndPrince 7 { 8 #region Nested types 9 10 class Node 11 { 12 #region Nested types 13 14 public class Gem 15 { 16 public int Row { get; set; } 17 public int Col { get; set; } 18 } 19 20 public class Connective 21 { 22 public readonly List<Gem> Connected = new List<Gem>(); 23 } 24 25 #endregion 26 27 #region Properties 28 29 private int[,] Gems { get; set; } 30 public List<Connective> Connectives { get; private set; } 31 public int Try { get; set; } 32 public int Score { get; private set; } 33 34 #endregion 35 36 #region Constructors 37 38 public Node(int rows, int cols, int[,] gems, int iniScore) 39 { 40 _rows = rows; 41 _cols = cols; 42 Score = iniScore; 43 Connectives = new List<Connective>(); 44 Gems = gems; 45 Segment(); 46 } 47 48 #endregion 49 50 #region Methods 51 52 public string GetHash() 53 { 54 var hash = ""; 55 foreach (var gem in Gems) 56 { 57 hash += gem.ToString() + '0'; 58 } 59 return hash; 60 } 61 62 void AddToConnective(Connective connective, bool[,] visited, int r, int c) 63 { 64 visited[r, c] = true; 65 connective.Connected.Add(new Gem { Row = r, Col = c }); 66 var imin = Math.Max(0, r - 1); 67 var imax = Math.Min(_rows - 1, r + 1); 68 var jmin = Math.Max(0, c - 1); 69 var jmax = Math.Min(_cols - 1, c + 1); 70 var cur = Gems[r, c]; 71 for (var i = imin; i <= imax; i++) 72 { 73 for (var j = jmin; j <= jmax; j++) 74 { 75 if (visited[i, j]) continue; 76 var val = Gems[i, j]; 77 if (val == cur) 78 { // TODO recursive, improve it 79 AddToConnective(connective, visited, i, j); 80 } 81 } 82 } 83 } 84 85 void Segment() 86 { 87 var visited = new bool[_rows, _cols]; 88 for (var i = 0; i < _rows; i++) 89 { 90 for (var j = 0; j < _cols; j++) 91 { 92 if (visited[i, j] || Gems[i, j] == 0) continue; 93 var connective = new Connective(); 94 AddToConnective(connective, visited, i, j); 95 if (connective.Connected.Count < 3) continue; 96 Connectives.Add(connective); 97 } 98 } 99 } 100 101 public Node Action(int path) 102 { 103 var connective = Connectives[path]; 104 var gems = Copy(); 105 var firstNonZeroRow = _rows; 106 var firstNonZeroCol = 0; 107 108 foreach (var gem in connective.Connected) 109 { 110 gems[gem.Row, gem.Col] = 0; 111 } 112 // processes falling 113 var newcols = 0; 114 for (var i = 0; i < _cols; i++) 115 { 116 var k = _rows - 1; 117 for (var j = _rows - 1; j >= 0; j--) 118 { 119 if (gems[j, i] > 0) 120 { 121 gems[k--, i] = gems[j, i]; 122 } 123 } 124 for (var t = 0; t <= k; t++) 125 { 126 gems[t, i] = 0; 127 } 128 if (k + 1 < firstNonZeroRow) firstNonZeroRow = k + 1; 129 if (k + 1 < _rows) 130 { // non-empty 131 newcols++; 132 } 133 else if (i == firstNonZeroCol) 134 { 135 firstNonZeroCol = i; 136 } 137 } 138 // processes shifting 139 140 var newrows = _rows - firstNonZeroRow; 141 var newgems = new int[newrows, newcols]; 142 var tcol = 0; 143 for (var j = firstNonZeroCol; j < _cols; j++) 144 { 145 if (gems[_rows - 1, j] == 0) continue; // empty column 146 for (var i = firstNonZeroRow; i < _rows; i++) 147 { 148 newgems[i - firstNonZeroRow, tcol] = gems[i, j]; 149 } 150 tcol++; 151 } 152 var count = connective.Connected.Count; 153 var scoreInc = count*count; 154 return new Node(newrows, newcols, newgems, Score + scoreInc); 155 } 156 157 int[,] Copy() 158 { 159 var gems = new int[_rows,_cols]; 160 for (var i = 0; i < _rows; i++) 161 { 162 for (var j =0; j < _cols; j++) 163 { 164 gems[i, j] = Gems[i, j]; 165 } 166 } 167 return gems; 168 } 169 170 #endregion 171 172 #region Fields 173 174 private readonly int _rows; 175 private readonly int _cols; 176 177 #endregion 178 } 179 180 #endregion 181 182 #region Methods 183 184 /// <summary> 185 /// Solves the gem-and-prince problem presented in HDU-4090 186 /// http://acm.hdu.edu.cn/showproblem.php?pid=4090 187 /// found from the post at 188 /// http://blog.csdn.net/woshi250hua/article/details/7997550 189 /// </summary> 190 /// <param name="n">The number of rows the gem grid contains; might well be ignored in C#</param> 191 /// <param name="m">The number of columns the gem grid contains; might well be ignored in C#</param> 192 /// <param name="k">The inclusive upper bound of gem values of which the minimum is always 0 which means there is no gem</param> 193 /// <param name="gems">The initial grid of gems</param> 194 /// <returns>The highest score that can be achieved by the solution</returns> 195 public int Solve(int n, int m, int k, int[,] gems) 196 { 197 _records.Clear(); 198 var root = new Node(n, m, gems, 0); 199 var stack = new Stack<Node>(); 200 var highest = 0; 201 stack.Push(root); 202 203 while (stack.Count > 0) 204 { 205 var node = stack.Pop(); 206 207 208 if (node.Try >= node.Connectives.Count) continue; 209 var newNode = node.Action(node.Try); 210 node.Try++; 211 stack.Push(node); 212 213 #if true // optimisation 214 var hash = newNode.GetHash(); 215 if (_records.ContainsKey(hash)) 216 { 217 var oldScore = _records[hash]; 218 if (newNode.Score <= oldScore) 219 continue; 220 } 221 222 _records[hash] = newNode.Score; 223 #endif 224 225 if (newNode.Score > highest) 226 { 227 highest = newNode.Score; 228 } 229 stack.Push(newNode); 230 } 231 return highest; 232 } 233 234 #endregion 235 236 #region Fields 237 238 readonly Dictionary<string,int> _records = new Dictionary<string, int>(); 239 240 #endregion 241 } 242 }
测试程序和样本:
1 using System; 2 3 namespace HDU4090 4 { 5 class Program 6 { 7 struct Sample 8 { 9 public int N, M, K; 10 public int[,] Data; 11 } 12 13 private static Sample[] Samples = new Sample[] 14 { 15 new Sample 16 { 17 N = 5, 18 M = 5, 19 K = 5, 20 Data = 21 new[,] 22 { 23 {1, 2, 3, 4, 5}, 24 {1, 2, 2, 2, 1}, 25 {1, 2, 1, 2, 1}, 26 {1, 2, 2, 2, 2}, 27 {1, 2, 3, 3, 5} 28 } 29 }, 30 new Sample 31 { 32 N = 3, 33 M = 3, 34 K = 3, 35 Data = 36 new[,] 37 { 38 {1, 1, 1}, 39 {1, 1, 1}, 40 {2, 3, 3} 41 } 42 }, 43 new Sample 44 { 45 N = 4, 46 M = 4, 47 K = 3, 48 Data = 49 new[,] 50 { 51 {1, 1, 1, 3}, 52 {2, 1, 2, 3}, 53 {1, 2, 1, 3}, 54 {3, 3, 3, 3} 55 } 56 }, 57 new Sample 58 { 59 N = 4, 60 M = 4, 61 K = 2, 62 Data = 63 new[,] 64 { 65 {1, 2, 1, 2}, 66 {2, 1, 2, 1}, 67 {1, 2, 1, 2}, 68 {2, 1, 2, 1} 69 } 70 }, 71 new Sample 72 { 73 N = 8, 74 M = 8, 75 K = 6, 76 Data = 77 new[,] 78 { 79 {1, 1, 1, 1, 1, 1, 1, 1}, 80 {2, 2, 2, 2, 2, 2, 2, 2}, 81 {3, 3, 3, 3, 3, 3, 3, 3}, 82 {4, 4, 4, 4, 4, 4, 4, 4}, 83 {5, 5, 5, 5, 5, 5, 5, 5}, 84 {6, 6, 6, 6, 6, 6, 6, 6}, 85 {6, 6, 6, 6, 6, 6, 6, 6}, 86 {6, 6, 6, 6, 6, 6, 6, 6} 87 } 88 }, 89 new Sample 90 { 91 N = 8, 92 M = 8, 93 K = 6, 94 Data = 95 new[,] 96 { 97 {6, 6, 6, 6, 6, 6, 6, 6}, 98 {6, 6, 6, 6, 6, 6, 6, 6}, 99 {6, 6, 6, 6, 6, 6, 6, 6}, 100 {6, 6, 6, 6, 6, 6, 6, 6}, 101 {6, 6, 6, 6, 6, 6, 6, 6}, 102 {6, 6, 6, 6, 6, 6, 6, 6}, 103 {6, 6, 6, 6, 6, 6, 6, 6}, 104 {6, 6, 6, 6, 6, 6, 6, 6} 105 } 106 }, 107 }; 108 109 110 private static int[] _key = {166,36,94,128,896,4096}; 111 112 static void Main(string[] args) 113 { 114 var solve = new SolveGemAndPrince(); 115 var solve2 = new SolveGemAndPrince2(); 116 var time1 = DateTime.Now; 117 for (var i = 0; i < 10000; i++ ) 118 { 119 int t = 0; 120 foreach (var sample in Samples) 121 { 122 var result = solve.Solve(sample.N, sample.M, sample.K, sample.Data); 123 //var result = solve2.Solve(sample.N, sample.M, sample.K, sample.Data); 124 //Console.WriteLine("Highest score achievable = {0}", result); 125 if (result != _key[t++]) 126 { 127 Console.WriteLine("error!"); 128 return; 129 } 130 } 131 } 132 var time2 = DateTime.Now; 133 var span = time2 - time1; 134 Console.WriteLine("Done {0}", span.TotalSeconds); 135 } 136 } 137 }
将工程编译都调整到速度最大优化,编译环境为Visual Studio 2012,C++编译为32位,C#为Any CPU,运行于CORE i7。结果是运行10000次循环,算法相同的C++程序的速度大约是C#的6倍。当然这个倍率有点高于我的预料,可能程序中还有需要对针对C#进行改进和优化的地方(例如减少堆分配等),但同样C++也有提升空间(当然原作者已经做的很好了)。这个程序包含栈/递归和字典数据结构和一些逻辑和计算操作。总的来说在最大速度优先编译情况下,C++相对C#的执行效率优势还是比较明显。等过一阵子有空针对这个实例再做一次review和各自优化再评估一下结果。
关于效率这个问题,stackoverflow上有一些讨论可以参考:
http://stackoverflow.com/questions/145110/c-performance-vs-java-c
http://stackoverflow.com/questions/3961426/c-sharp-vs-c-performance-comparison
附,对应的C++程序(由原作者所作,略作修改并用于执行效率对比)
// HDU4090cpp.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <stdlib.h> #include <stdio.h> #include <map> #include <string> #include <string.h> #include <time.h> using namespace std; #define MAX 10 struct node { int x,y; }qu[MAX*MAX]; map<string,int> _hash; int gmmap[8][MAX][MAX]; int (*mmap)[MAX]; int ans; int n,m,K,total; int dir[8][2] = {{1,0},{1,1},{1,-1},{-1,0},{-1,-1},{-1,1},{0,1},{0,-1}}; void Print(int temp[][MAX],int n,int m) { for (int i = n-1; i >= 0; --i) for (int j = 0; j < m; ++j) printf("%d%c",temp[i][j],j==m-1?'\n':' '); } void change(int mmap[][MAX],int &n,int &m){ int i,j,k[10],tn = 0,tm = 0; for (j = 0; j < m; ++j) { k[j] = 0; for (i = 0; i < n; ++i) if (mmap[i][j]) mmap[k[j]++][j] = mmap[i][j]; } for (j = 0; j < m; ++j) if (k[j]) { for (i = 0; i < k[j]; ++i) mmap[i][tm] = mmap[i][j]; for (i = k[j]; i < n; ++i) mmap[i][tm] = 0; tm++; if (k[j] > tn) tn = k[j]; } n = tn,m = tm; } int Ok(int temp[][MAX],int vis[][MAX],int i,int j,int n,int m) { int cnt = 0,k; int head = 0,tail = 0; node cur,next; cur.x = i,cur.y = j; qu[head++] = cur; vis[cur.x][cur.y] = 1; while (tail < head) { cur = qu[tail++]; cnt++; for (k = 0; k < 8; ++k) { next.x = cur.x + dir[k][0]; next.y = cur.y + dir[k][1]; if (next.x >= 0 && next.x < n && next.y >= 0 && next.y < m && vis[next.x][next.y] == 0 && temp[next.x][next.y] == temp[i][j]) { qu[head++] = next; vis[next.x][next.y] = 1; temp[next.x][next.y] = 0; } } } temp[i][j] = 0; return cnt >= 3 ? cnt : 0; } string Get_hash(int temp[][MAX],int n,int m) { string s = ""; for (int i = 0; i < n; ++i) for (int j = 0; j < m; ++j) s += temp[i][j] + '0'; return s; } void mem(int temp[][MAX],int mmap[][MAX],int n,int m) { memset(temp,0,sizeof(temp)); for (int i = 0; i < n; ++i) for (int j = 0; j < m; ++j) temp[i][j] = mmap[i][j]; } int Dfs(int mmap[][MAX],int n,int m) { if (n * m < 3) return 0; int temp[MAX][MAX],i,j; int vis[MAX][MAX],cnt = 0,ans; memset(vis,0,sizeof(vis)); for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) if (!vis[i][j] && mmap[i][j]) { mem(temp,mmap,n,m); ans = Ok(temp,vis,i,j,n,m); if (ans >= 3) { ans = ans * ans; int tn = n,tm = m; change(temp,tn,tm); string s = Get_hash(temp,tn,tm); if (_hash.find(s) == _hash.end()) ans += Dfs(temp,tn,tm); else ans += _hash[s]; if (ans > cnt) cnt = ans; } } _hash[Get_hash(mmap,n,m)] = cnt; return cnt; } int _tmain(int argc, _TCHAR* argv[]) { int i,j,k; int key[] = {166,36,94,128,896,4096}; int t=0; FILE *fp = fopen("D:\\temp\\samples.txt", "r"); int testnum = 0; while (fscanf(fp, "%d%d%d",&n,&m,&K) != EOF) { for (i = n-1; i >= 0; --i) for (j = 0; j < m; ++j) fscanf(fp, "%d",&gmmap[testnum][i][j]); testnum++; } time_t t1; time(&t1); for (int C = 0; C < 10000; C++) { t = 0; for (int t=0; t<testnum; t++) { _hash.clear(); int res = Dfs(gmmap[t],n,m); if (res != key[t]) { printf("error\n"); return 0; } t++; } } time_t t2; time(&t2); double diff=difftime(t2,t1); printf("done %f\n", diff); fclose(fp); }