dsa算法(13)
来源:互联网 发布:pop3协议默认端口号 编辑:程序博客网 时间:2024/05/21 09:52
1.3.3.4.1.5. 删除死节点
至此,我们结束了对当前函数的DSGraph的处理。接下来,使用可到达性分析来删除其中不可访问的节点。这样可以加速后续的处理。
GraphBuilder:: GraphBuilder(续)
205 // Remove anynodes made dead due to merging...
206 g.removeDeadNodes(DSGraph::KeepUnreachableGlobals);
207 }
1052 void DSGraph::removeDeadNodes(unsigned Flags) {
1053 DEBUG(AssertGraphOK(); if (GlobalsGraph) GlobalsGraph->AssertGraphOK());
1054
1055 // Reduce theamount of work we have to do... remove dummy nodes left over by
1056 // merging...
1057 removeTriviallyDeadNodes();
首先是删除显而易见的死节点。死节点是没有被使用的节点,它们不需要通过可到达性分析来查找。
915 void DSGraph::removeTriviallyDeadNodes(){
916 /// NOTE: This codeis disabled. This slows down DSA on177.mesa
917 /// substantially!
918
919 // Loop over all ofthe nodes in the graph, calling getNode on each field.
920 // This will causeall nodes to update their forwarding edges, causing
921 // forwarded nodesto be delete-able. Further, reclaim anymemory used by
922 // useless edge ortype entries
923 for(node_iterator NI = node_begin(), E = node_end(); NI != E; ++NI)
924 for(DSNode::edge_iterator ii = NI->edge_begin(), ee = NI->edge_end();
925 ii != ee; ++ii) {
926 ii->second.getNode()->cleanEdges();
927 }
928
929 // Likewise,forward any edges from the scalar nodes. While we are at it,
930 // clean house abit.
931 for(DSScalarMap::iterator I = ScalarMap.begin(), E = ScalarMap.end();
932 I != E; ++I)
933 I->second.getNode();
在923、924行的循环遍历图中每个对象(由DSNode代表)中的每个指针域(由DSNodeHandle代表),对所指向的对象进行清理整顿。我们知道为了尽可能全面地记录源语言中的对象,DSNode在TyMap中按偏移记录了对象所包含的类型,同时在Links中也按偏移记录了对象所包含的指针连同指向的对象(即下面所谓的边——edge)。那么705行的循环清除了TyMap中的空类型,而716行则清除了Links中的空指针。
703 void DSNode::cleanEdges(){
704 //get rid of anytype edge pointing to the null type
705 for(type_iterator ii = type_begin(); ii != type_end(); ) {
706 if (ii->second)
707 ++ii;
708 else {
709 type_iterator backup = ii;
710 ++backup;
711 TyMap.erase(ii);
712 ii = backup;
713 }
714 }
715 //get rid of anynode edge pointing to nothing
716 for(edge_iterator ii = edge_begin(); ii != edge_end(); ) {
717 if (ii->second.isNull()) {
718 edge_iterator backup = ii;
719 ++backup;
720 Links.erase(ii);
721 ii= backup;
722 } else
723 ++ii;
724 }
725 }
在DSGraph中ScalarMap的作用是记录对象与援引它的指针(通过DSNodeHandle)。933行的getNode方法迫使转发节点得到处理。这样,也会清理掉一些转发节点。
DSGraph::removeTriviallyDeadNodes(续)
935 bool isGlobalsGraph = !GlobalsGraph;
936
937 for (NodeListTy::iteratorNI = Nodes.begin(), E = Nodes.end(); NI != E; ) {
938 DSNode &Node = *NI;
939
940 // Do not remove*any* global nodes in the globals graph.
941 // This is aspecial case because such nodes may not have I, M, R flags set.
942 if(Node.isGlobalNode() && isGlobalsGraph) {
943 ++NI;
944 continue;
945 }
946
947 if (Node.isCompleteNode() &&!Node.isModifiedNode() && !Node.isReadNode()) {
948 // This is auseless node if it has no mod/ref info (checked above),
949 // outgoingedges (which it cannot, as it is not modified in this
950 // context),and it has no incoming edges. If it is aglobal node it may
951 // have all ofthese properties and still have incoming edges, due to the
952 // scalar map, so we check those now.
953 //
954 if (Node.getNumReferrers() ==Node.numGlobals()) {
955
956 // Loopthrough and make sure all of the globals are referring directly
957 // to thenode...
958 for(DSNode::globals_iterator j = Node.globals_begin(), e = Node.globals_end();
959 j != e; ++j) {
960 getNodeForValue(*j).getNode();
961 assert((getNodeForValue(*j).getNode())== &Node && "ScalarMap doesn't match globals list!");
962 }
963
964 // Make sureNumReferrers still agrees, if so, the node is truly dead.
965 if (Node.getNumReferrers() ==Node.numGlobals()) {
966 for(DSNode::globals_iterator j = Node.globals_begin(), e = Node.globals_end();
967 j != e; ++j)
968 if (ScalarMap.find(*j) !=ScalarMap.end())
969 ScalarMap.erase(*j);
970 Node.makeNodeDead();
971 ++NumTrivialGlobalDNE;
972 }
973 }
974 }
975
976 if ((Node.getNodeFlags() == 0 &&Node.hasNoReferrers())
977 || (isGlobalsGraph &&Node.hasNoReferrers() && !Node.isGlobalNode())){
978 // This node isdead!
979 NI = Nodes.erase(NI); // Erase &remove from node list.
980 ++NumTrivialDNE;
981 } else {
982 ++NI;
983 }
984 }
985 #if 0
986 #endif
987 removeIdenticalCalls(FunctionCalls);
988 removeIdenticalCalls(AuxFunctionCalls);
989 }
对于函数图,935行的GlobalsGraph指向全局图(即DataStructures中的GlobalsGraph),因此它们将跳过所有的全局节点。而对于全局图,这个指针为空的,因此它会执行947~983行。这段代码只考虑完整,而且没有读写的节点。在954行,numGlobals是Globals的大小,Globals记录了该DSNode所代表的全局对象(这些对象共享这个DSNode),而getNumReferrers则返回该DSNode被援引的计数(其中Globals中的对象贡献numGlobals个计数)。如果两者相等,说明这些全局对象没有额外的引用,可以删除。首先通过getNode(960行)清除相关转发节点,然后删除。注意970行的makeNodeDead,它会使976行的getNodeFlags()== 0条件得到满足,从Nodes中删除这个节点。
最后,还要调用removeIdenticalCalls删除相同的函数调用。
847 static voidremoveIdenticalCalls(DSGraph::FunctionListTy&Calls) {
848 // Remove triviallyidentical function calls
849
850 unsigned NumDeleted = 0;
851
852 // First, scanthrough killing off useless edges and trivially dead callsites
853 for(DSGraph::FunctionListTy::iterator I = Calls.begin(), E = Calls.end();
854 I != E; ) {
855 DSCallSite &CS = *I;
856
857 // If the returnvalue or any arguments point to a void node with no
858 // information atall in it, and the call node is the only node to point
859 // to it, removethe edge to the node (killing the node).
860 //
861 killIfUselessEdge(CS.getRetVal());
862 killIfUselessEdge(CS.getVAVal());
863 for(unsigned a = 0, e = CS.getNumPtrArgs(); a != e; ++a)
864 killIfUselessEdge(CS.getPtrArg(a));
865
866 // If this is anindirect callsite, but the Callee DSNode isn't
867 // tied to fromanything, remove it trivially.
868 if (CS.isIndirectCall()) {
869 DSNode *Callee = CS.getCalleeNode();
870 if (Callee->getNumReferrers() == 1&& Callee->isCompleteNode() &&
871 Callee->isEmptyGlobals()) { // No useful info?
872 DEBUG(errs() << "WARNING:Useless call site found.\n");
873 I = Calls.erase(I);
874 E = Calls.end();
875 ++NumDeleted;
876 continue;
877 }
878 }
879 ++I;
880 }
853行的Calls包含了图中的所有函数调用指令。对这些调用指令分别检查返回值、变长参数、指针参数所指向的对象是否可以删除。判断的标准由是killIfUselessEdge给出:对象只有这个引用,只有未完成标记,并且整体缺乏类型信息(TyMap完全是空的,842行),而且还不能是缩合的。
837 staticinlinevoid killIfUselessEdge(DSNodeHandle &Edge) {
838 if (DSNode * N = Edge.getNode())// Is there an edge?
839 if (N->getNumReferrers() == 1) // Does it point toa lonely node?
840 // Nointeresting info?
841 if ((N->getNodeFlags() &~DSNode::IncompleteNode) == 0
842 && N->hasNoType()
843 && !N->isNodeCompletelyFolded())
844 Edge.setTo(0, 0); // Kill the edge!
845 }
844行的setTo会递减所指向DSNode对象的NumReferrers计数,这使得该对象成为孤悬,为后面回收做好准备。
868行,如果是完整的间接函数调用,而实际没有调用任何对象(注意isEmptyGlobals条件,如果被调用对象不是Function或GlobalValue,满足此条件),而且我们是唯一的使用者,删除这个CallSite。
removeIdenticalCalls(续)
882 // Now scan forredundant indirect callsites
883 // First, sort bycallee (using DSCallSite::operator<)
884 sort(Calls);
885
886 // Then findadjacent callsites that are equivalent and handle accordingly
887 DSGraph::FunctionListTy::iterator I =Calls.begin();
888 while((I =std::adjacent_find(I, Calls.end())) != Calls.end()) {
889 DSGraph::FunctionListTy::iterator Second =I;
890 DSCallSite &DCS1 = *I, &DCS2 =*++Second;
891
892 if (DCS1.isIndirectCall()) {
893 // Merge themtogether (into the first one)
894 DCS1.mergeWith(DCS2);
895 }
896
897 // Remove thesecond one
898 ++NumDeleted;
899 Calls.erase(Second);
900
901 // Carry on,searching from 'I' (first one)...
902 }
903
904 // Track the numberof call nodes merged away...
905 NumCallNodesMerged += NumDeleted;
906
907 if (NumDeleted)
908 DEBUG(errs() << "Merged "<< NumDeleted << " call nodes.\n");
909 }
随后,在884行对CallSite进行排序。DSCallSite定义了“<”操作符,定义间接调用“小于”直接的调用。
327 bool operator<(const DSCallSite &CS) const{
328 if (isDirectCall()) { // This mustsort by callee first!
329 if (CS.isIndirectCall())return true;
330 if (CalleeF < CS.CalleeF)return true;
331 if (CalleeF > CS.CalleeF)return false;
332 } else {
333 if (CS.isDirectCall())return false;
334 if (CalleeN < CS.CalleeN)return true;
335 if (CalleeN > CS.CalleeN)return false;
336 }
337 if (RetVal < CS.RetVal)return true;
338 if (RetVal > CS.RetVal)return false;
339 if (VarArgVal < CS.VarArgVal)return true;
340 if (VarArgVal > CS.VarArgVal)return false;
341 returnCallArgs < CS.CallArgs;
342 }
CallSite的成员CalleeF的类型是Function*,而成员CalleeN的类型是DSNodeHandle。DSNodeHandle重载了如下的“<”操作符,首先调用的getNode方法会处理转发节点,然后在“||”的左侧就可以直接使用“N == H.N”进行比较。
78 bool operator<(const DSNodeHandle &H) const{ // Allowsorting
79 return getNode() < H.getNode() || (N == H.N&& Offset < H.Offset);
80 }
如果出现重复的间接函数调用(如函数指针),通过下面的方法对CallSite进行简并。DSCallSite中的MappedSites用于记录进行了简并的CallSite。对于同一个函数不同的调用点,真正需要简并的是返回值,变长参数及指针参数,DSA把不同调用点间的这些参数视为互为别名。
305 void mergeWith(DSCallSite&CS) {
306 getRetVal().mergeWith(CS.getRetVal());
307 getVAVal().mergeWith(CS.getVAVal());
308 unsigned MinArgs = getNumPtrArgs();
309 if (CS.getNumPtrArgs() < MinArgs)MinArgs = CS.getNumPtrArgs();
310
311 for(unsigned a = 0; a != MinArgs; ++a)
312 getPtrArg(a).mergeWith(CS.getPtrArg(a));
313
314 for(unsigned a = MinArgs, e = CS.getNumPtrArgs(); a != e; ++a)
315 CallArgs.push_back(CS.getPtrArg(a));
316
317 MappedSites.insert(CS.getCallSite());
318 MappedSites.insert(CS.ms_begin(),CS.ms_end());
319 }
现在由可到达性分析来识别其他死节点了。这里根据Flags中标志位的设定,有KeepUnreachableGlobals及RemoveUnreachableGlobals的区别。RemoveUnreachableGlobals只保留从局部对象可到达的全局对象,而KeepUnreachableGlobals将保留从局部对象可到达的全局对象,以及可到达这些局部对象的全局对象。
在1076行的ReachabilityCloner构造函数中,目标图是GlobalsGraph,源图是当前函数图。参数Flags是KeepUnreachableGlobals,GGCloner会克隆所有的全局对象,以及调用点(RemoveUnreachableGlobals时不需要显式克隆可到达的全局对象,因为通过当前函数的局部变量可以访问它们)。
DSGraph::removeDeadNodes(续)
1059 // FIXME: Mergenon-trivially identical call nodes...
1060
1061 // Alive - a setthat holds all nodes found to be reachable/alive.
1062 DenseSet<constDSNode*> Alive;
1063 std::vector<std::pair<const Value*, DSNode*> > GlobalNodes;
1064
1065 // Copy and mergeall information about globals to the GlobalsGraph if this is
1066 // not a final pass(where unreachable globals are removed).
1067 //
1068 // Strip all allocabits since we are merging information into the globals
1069 // graph.
1070 // Strip allincomplete bits since they are short-lived properties and they
1071 // will becorrectly computed when rematerializing nodes into the functions.
1072 //
1073 // This code mergesinformation learned about the globals in 'this' graph
1074 // back into theglobals graph, before it deletes any such global nodes,
1075 // (with some newinformation possibly) from 'this' current function graph.
1076 ReachabilityCloner GGCloner(GlobalsGraph,this, DSGraph::StripAllocaBit |
1077 DSGraph::StripIncompleteBit);
1078
1079 // Mark all nodesreachable by (non-global) scalar nodes as alive...
1080 for(DSScalarMap::iterator I = ScalarMap.begin(), E = ScalarMap.end();
1081 I != E; ++I)
1082 if (isa<GlobalValue > (I->first)){// Keep track of global nodes
1083 assert(!I->second.isNull()&& "Null global node?");
1084 assert(I->second.getNode()->isGlobalNode()&& "Should be a global node!");
1085 GlobalNodes.push_back(std::make_pair(I->first,I->second.getNode()));
1086
1087 // Make surethat all globals are cloned over as roots.
1088 if (!(Flags &DSGraph::RemoveUnreachableGlobals) && GlobalsGraph) {
1089 GGCloner.getClonedNH(I->second);
1090 }
1091 } else {
1092 I->second.getNode()->markReachableNodes(Alive);
1093 }
1094
1095 // The returnvalues are alive as well.
1096 for(ReturnNodesTy::iterator I = ReturnNodes.begin(), E = ReturnNodes.end();
1097 I != E; ++I)
1098 I->second.getNode()->markReachableNodes(Alive);
1099
1100 // Mark any nodesreachable by primary calls as alive...
1101 for(fc_iterator I = fc_begin(), E = fc_end(); I != E; ++I)
1102 I->markReachableNodes(Alive);
这里全局对象不被标记为可到达,在1085行它们暂时记录在GlobalNodes中。而局部对象、返回值及直接调用函数的指令都视为可到达(1092、1098、1102行)。
1167 void DSNode::markReachableNodes(DenseSet<const DSNode*> &ReachableNodes)const {
1168 if (this == 0) return;
1169 assert(!isForwarding()&& "Cannot mark a forwarded node!");
1170 if (ReachableNodes.insert(this).second)// Is newly reachable?
1171 for(DSNode::const_edge_iterator I = edge_begin(), E = edge_end();
1172 I != E; ++I)
1173 I->second.getNode()->markReachableNodes(ReachableNodes);
1174 }
markReachableNodes将深度遍历节点指针域所指向的对象节点,所有通过这些对象可以访问的对象都保存在Alive中。
DSGraph::removeDeadNodes(续)
1105 // Now find globalsand aux call nodes that are already live or reach a live
1106 // value (whichmakes them live in turn), and continue till no more are found.
1107 //
1108 bool Iterate;
1109 DenseSet<constDSNode*> Visited;
1110 std::set<constDSCallSite*> AuxFCallsAlive;
1111 do {
1112 Visited.clear();
1113 // If any globalnode points to a non-global that is "alive", the global is
1114 //"alive" as well... Remove itfrom the GlobalNodes list so we only have
1115 // unreachableglobals in the list.
1116 //
1117 Iterate = false;
1118 if (!(Flags &DSGraph::RemoveUnreachableGlobals))
1119 for(unsigned i = 0; i != GlobalNodes.size(); ++i)
1120 if (CanReachAliveNodes(GlobalNodes[i].second,Alive, Visited,
1121 Flags &DSGraph::RemoveUnreachableGlobals)) {
1122 std::swap(GlobalNodes[i--], GlobalNodes.back());// Move to end to...
1123 GlobalNodes.pop_back(); // erase efficiently
1124 Iterate = true;
1125 }
1126
1127 // Mark onlyunresolvable call nodes for moving to the GlobalsGraph since
1128 // call nodesthat get resolved will be difficult to remove from that graph.
1129 // The finalunresolved call nodes must be handled specially at the end of
1130 // the BU pass(i.e., in main or other roots of the call graph).
1131 for(afc_iterator CI = afc_begin(), E = afc_end(); CI != E; ++CI)
1132 if (!AuxFCallsAlive.count(&*CI)&&
1133 (CI->isIndirectCall()
1134 || CallSiteUsesAliveArgs(*CI,Alive, Visited,
1135 Flags &DSGraph::RemoveUnreachableGlobals))) {
1136 CI->markReachableNodes(Alive);
1137 AuxFCallsAlive.insert(&*CI);
1138 Iterate = true;
1139 }
1140 } while(Iterate);
如果Flags设置了RemoveUnreachableGlobals位,直接跳过1119~1124行。而对于KeepUnreachableGlobals,CanReachAliveNodes的参数IgnoreGlobals将是false,会检查函数图中的每个全局对象是否可到达,从GlobalNodes删除中可到达的全局对象。在CanReachAliveNodes中的参数Alive记录已确认可到达的对象,Visited则记录指定的节点是否已经处理过了。
996 static boolCanReachAliveNodes(DSNode *N, DenseSet<const DSNode*> &Alive,
997 DenseSet<const DSNode*> &Visited,
998 boolIgnoreGlobals) {
999 if (N == 0) returnfalse;
1000 assert(N->isForwarding()== 0 && "Cannot mark a forwarded node!");
1001
1002 // If this is aglobal node, it will end up in the globals graph anyway, so we
1003 // don't need toworry about it.
1004 if (IgnoreGlobals &&N->isGlobalNode())return false;
1005
1006 // If we know that this node is alive, returnso!
1007 if (Alive.count(N)) returntrue;
1008
1009 // Otherwise, wedon't think the node is alive yet, check for infinite
1010 // recursion.
1011 if (Visited.count(N)) return false; // Found a cycle
1012 Visited.insert(N); // No recursion,insert into Visited...
1013
1014 for(DSNode::edge_iterator I = N->edge_begin(),E = N->edge_end(); I != E;++I)
1015 if(CanReachAliveNodes(I->second.getNode(), Alive, Visited, IgnoreGlobals)) {
1016 N->markReachableNodes(Alive);
1017 returntrue;
1018 }
1019 return false;
1020 }
在1015行,我们沿着DSNode的Links递归调用CanReachAliveNodes,最后要么碰到以确认可到达的节点,要么碰到的都是已访问过的节点(1014行循环在CanReachAliveNodes返回false时,不结束)。
1025 static boolCallSiteUsesAliveArgs(const DSCallSite &CS,
1026 DenseSet<const DSNode*> &Alive,
1027 DenseSet<const DSNode*> &Visited,
1028 boolIgnoreGlobals) {
1029 if (CanReachAliveNodes(CS.getRetVal().getNode(),Alive, Visited,
1030 IgnoreGlobals))
1031 returntrue;
1032 if(CanReachAliveNodes(CS.getVAVal().getNode(), Alive, Visited, IgnoreGlobals))
1033 returntrue;
1034 if (CS.isIndirectCall() &&
1035 CanReachAliveNodes(CS.getCalleeNode(),Alive, Visited, IgnoreGlobals))
1036 returntrue;
1037 for (unsignedi = 0, e = CS.getNumPtrArgs(); i != e; ++i)
1038 if(CanReachAliveNodes(CS.getPtrArg(i).getNode(), Alive, Visited,
1039 IgnoreGlobals))
1040 returntrue;
1041 return false;
1042 }
对于间接调用,我们在前面已经把重复的调用清除了,因此在这里就把它们标记为可到达。而其他情形的调用则由CallSiteUsesAliveArgs处理,其中的参数IgnoreGlobals与CanReachAliveNodes有相同的含义。
如果我们成功地标记了新的可到达节点,必须重新运行1111行的循环,直到没有新的可到达节点被发现。
DSGraph::removeDeadNodes(续)
1142 // If only some ofthe aux calls are alive
1143 if (AuxFCallsAlive.size() !=AuxFunctionCalls.size()) {
1144 // Move dead auxfunction calls to the end of the list
1145 FunctionListTy::iterator Erase =AuxFunctionCalls.end();
1146 for(FunctionListTy::iterator CI = AuxFunctionCalls.begin(); CI != Erase; )
1147 if (AuxFCallsAlive.count(&*CI))
1148 ++CI;
1149 else {
1150 // Copy andmerge global nodes and dead aux call nodes into the
1151 //GlobalsGraph, and all nodes reachable from those nodes. Update their
1152 // targetpointers using the GGCloner.
1153 //
1154 if (!(Flags &DSGraph::RemoveUnreachableGlobals))
1155 GlobalsGraph->AuxFunctionCalls.push_back(DSCallSite(*CI, GGCloner));
1156
1157 std::swap(*CI, *--Erase);
1158 }
1159 AuxFunctionCalls.erase(Erase,AuxFunctionCalls.end());
1160 }
1161 AuxFCallsAlive.clear();
1162
1163 // We are finallydone with the GGCloner so we can destroy it.
1164 GGCloner.destroy();
1165
1166 // At this point, anynodes which are visited, but not alive, are nodes
1167 // which can beremoved. Loop over all nodes,eliminating completely
1168 // unreachablenodes.
1169 //
1170 std::vector<DSNode*> DeadNodes;
1171 DeadNodes.reserve(Nodes.size());
1172 for(NodeListTy::iterator NI = Nodes.begin(), E = Nodes.end(); NI != E;) {
1173 DSNode *N = NI++;
1174 assert(!N->isForwarding()&& "Forwarded node in nodes list?");
1175
1176 if (!Alive.count(N)) {
1177 Nodes.remove(N);
1178 assert(!N->isForwarding()&& "Cannot remove a forwarding node!");
1179 DeadNodes.push_back(N);
1180 N->dropAllReferences();
1181 ++NumDNE;
1182 }
1183 }
1184
1185 // Remove allunreachable globals from the ScalarMap.
1186 // If flagRemoveUnreachableGlobals is set, GlobalNodes has only dead nodes.
1187 // In either case,the dead nodes will not be in the set Alive.
1188 for (unsignedi = 0, e = GlobalNodes.size(); i != e; ++i)
1189 if (!Alive.count(GlobalNodes[i].second))
1190 ScalarMap.erase(GlobalNodes[i].first);
1191 else
1192 assert((Flags& DSGraph::RemoveUnreachableGlobals) && "non-deadglobal");
1193
1194 // Delete all deadnodes now since their referrer counts are zero.
1195 for (unsignedi = 0, e = DeadNodes.size(); i != e; ++i)
1196 deleteDeadNodes[i];
1197
1198 DEBUG(AssertGraphOK();GlobalsGraph->AssertGraphOK());
1199 }
如果在前面1131行的循环中,某些AuxFunctionCalls元素被识别为不可到达,在1146行开始的循环查找这些元素,然后在1155行把这些元素加入全局图(通过GGCloner找到全局图中对应对象,前面1089行已经准备好这些节点),并最终从函数图中删除这些元素。在前面的处理中,Alive保存了可到达节点的地址,接下来的循环则根据Alive的记录删除这些节点。如果Flags没有设置DSGraph::RemoveUnreachableGlobals标记,将删除所有从局部变量不能到达的GlobalValue。因为在RemoveUnreachableGlobals的情况下,GlobalNodes保留了所有的全局对象,因此可能不满足1189行条件。但KeepUnreachableGlobals不会。
- dsa算法(13)
- dsa算法(1)
- dsa算法(2)
- dsa算法(3)
- dsa算法(4)
- dsa算法(5)
- dsa算法(6)
- dsa算法(7)
- dsa算法(8)
- dsa算法(9)
- dsa算法(10)
- dsa算法(11)
- dsa算法(12)
- dsa算法(14)
- dsa算法(15)
- dsa算法(16)
- dsa算法(17)
- dsa算法(18)
- Nginx的配置与部署4)Nginx常用命令
- 动漫推荐之恋爱随意链接
- 解决Xcode上传出现的Error ITMS-9000问题
- sem_init,sem_post,sem_wait 信号量的用法解释
- 调用系统计算器 android(适用于不同品牌)
- dsa算法(13)
- Starting Tomcat v7.0 Server at localhost' has encountered a problem.
- java导出成jar(含有第三方jar和配置文件)
- android控件显示顺序控制
- 在Unity3D中使用ScriptableObject进行序列化
- Cocos2d-x下Lua调用自定义C++类和函数的最佳实践
- JAVA操作properties文件
- 《Deep Learning Face Representaion from Predicting 10000 Classes》读书报告
- JavaFX 使用实例