当前位置: 首页 > news >正文

Paddle build_cinn_pass_test源码阅读(fluid目录下)

代码位置在 paddle\fluid\framework\paddle2cinn\build_cinn_pass_test.cc ,因为paddle CINN和PIR部分依旧在高频更新,所以各位看到的可能和我的不一样

inline bool CheckNodeExisted(const std::unordered_set<Node*>& nodes,const std::string& op_name) {return std::find_if(nodes.begin(), nodes.end(), [&op_name](const Node* node) {return node->Name() == op_name;}) != nodes.end();
}

用一个内联函数, 去看一个 unordered_set (一系列节点) 中是否有某个 node 的名字是 op_name,用 std::find_if 去实现, 第三个参数传入的是匿名函数。[&op_name] 闭包被定义在Lambda表达式声明中的方括号[]内. 这个机制允许这些变量被按值或按引用捕获.

函数匿名函数的闭包可以参考这篇文章: https://www.cnblogs.com/pzhfei/archive/2013/01/14/lambda_expression.html

接下来就是返回名字为 op_namenode 数量

inline int CountNode(const std::unordered_set<Node*>& nodes,const std::string& op_name) {return std::count_if(nodes.begin(), nodes.end(), [&op_name](const Node* node) {return node->Name() == op_name;});
}

接下来是返回节点名字是 op_name 的 节点,注意 std::find_if 前面为啥有 * 呢,因为 find_if 返回一个迭代器, *迭代器 可以返回一个 Node*

inline Node* GetNode(const std::unordered_set<Node*>& nodes,const std::string& op_name) {return *std::find_if(nodes.begin(), nodes.end(), [&op_name](const Node* node) {return node->Name().find(op_name) != std::string::npos;});
}

CheckGraphIndependence 内部定义了一个 check_node_ok 匿名函数,匿名函数中 n1n2 都是节点 Node 的指针,
( 说明一下,Paddle PIR之前的节点,节点既有 Op, 也有 Var )
只有 n1n2 一个为 OP, 一个为 Var 才有可能返回 true;

inline bool CheckGraphIndependence(const std::unordered_set<Node*>& nodes) {auto check_node_ok = [&nodes](Node* n1, Node* n2) -> bool {if (n1->IsOp() && !n2->IsVar()) {return false;}if (n1->IsVar() && !n2->IsOp()) {return false;}if (nodes.count(n2) == 0) {return false;}return true;};for (auto node : nodes) {for (auto in : node->inputs) {if (!check_node_ok(node, in)) {return false;}}for (auto out : node->outputs) {if (!check_node_ok(node, out)) {return false;}}}return true;
}

这里需要说明一下,由于 Paddle pir之前 Op 和 Var 都是node, 所以这样定义

var1 -> op1 -> var2
op3-> var3 -> op4

op1的输入是 var1,输出是 var2,而下边那一行是
va3 的输入是 op3,var3 的输出是 op4 , 这样写有点儿诡异,不过确实是这样定义的

所以 CheckGraphIndependence 的用法就是,首先检查是不是 op->varvar->op 的关系,其次就是看当前 op/var 在不在当前 Graph 的 unordered_set<Node*>

可以看到之后的调用就是将计算图的节点 g->Nodes() 传入 CheckGraphIndependence,如果返回值不为 True 则报错

  ASSERT_TRUE(CheckGraphIndependence(g->Nodes()));

这个函数主要是将 kCinnLaunchOpoperators::kCompilationKey 属性取出来扔到 compilation_keys这个 vector 中, 目前暂时未知有什么用

// Get compilation_key values
std::vector<int64_t> GetCompilationKeys(const Graph& graph) {std::vector<int64_t> compilation_keys;for (auto& node : graph.Nodes()) {if (node->IsOp() && node->Name() == kCinnLaunchOp) {compilation_keys.emplace_back(PADDLE_GET_CONST(int64_t, node->Op()->GetAttr(operators::kCompilationKey)));}}return compilation_keys;
}

接下来创建一个CINN子图,创建一个空图 Graph, 之后依次添加 op 和 var

std::unique_ptr<Graph> BuildNoCinnSubgraph() {ProgramDesc prog;auto g = std::make_unique<Graph>(prog);// var1 --//        | --> fake1 --> var3 --> fake2 --> var4// var2 --// *Desc 是之后用来创建 OpNode 和 VarNode 的类OpDesc fake1_op;fake1_op.SetType("fake1");OpDesc fake2_op;fake2_op.SetType("fake2");VarDesc var1("var1");VarDesc var2("var2");var2.SetPersistable(true);var2.SetIsParameter(true);VarDesc var3("var3");VarDesc var4("var4");// 之后用 graph 的 Create*Node 来创建对应的 ir::Nodeir::Node* fake1 = g->CreateOpNode(&fake1_op);ir::Node* fake2 = g->CreateOpNode(&fake2_op);ir::Node* v1 = g->CreateVarNode(&var1);ir::Node* v2 = g->CreateVarNode(&var2);ir::Node* v3 = g->CreateVarNode(&var3);ir::Node* v4 = g->CreateVarNode(&var4);// ----------- 创建完 node 之后, 把 op/var 串起来// fill op nodefake1->inputs = {v1, v2};fake1->outputs = {v3};fake2->inputs = {v3};fake2->outputs = {v4};// fill variable nodev1->outputs = {fake1};v2->outputs = {fake1};v3->inputs = {fake1};v3->outputs = {fake2};v4->inputs = {fake2};return g;
}

接下来出现第一个单测

TEST(BuildCinnPassTest, NoCinnSubgraph) {auto g = BuildNoCinnSubgraph();    // 调用上边的函数建计算图auto previous_nodes = g->Nodes();  // 取出计算图的节点// 创建 pass 这个应该是旧IR的passauto pass =paddle::framework::ir::PassRegistry::Instance().Get("build_cinn_pass");// g.get() 返回的是图的指针, g是个 unique_ptr 的智能指针pass->Apply(g.get());// After search, origin graph should no change// 注释的意思是, pass search 之后, 原来的计算图不应当修改ASSERT_EQ(previous_nodes, g->Nodes());ASSERT_TRUE(CheckGraphIndependence(g->Nodes())); // 接下来看计算图是否合法且不依赖其他计算图// After search, there should be no cinn subgraphASSERT_TRUE(GetCompilationKeys(*g).empty());  // pass search之后没有 cinn subgraph 子图怎么理解
}

接下来依旧是 BuildAllOpSupportCinnGraph 与上一个建图的函数没啥太大区别

  • 图更加复杂
  • op 的 type 从 fake2 变成了 elementwise_add | mul | relu
std::unique_ptr<Graph> BuildAllOpSupportCinnGraph() {ProgramDesc prog;auto g = std::make_unique<Graph>(prog);// v1 --//      | --> mul --> v3 --// v2 --                   | --> add --> v5 --> relu --> v6//                    v4 --OpDesc add_op;add_op.SetType("elementwise_add");OpDesc mul_op;mul_op.SetType("mul");OpDesc relu_op;relu_op.SetType("relu");VarDesc var1("var1");VarDesc var2("var2");var2.SetPersistable(true);var2.SetIsParameter(true);VarDesc var3("var3");VarDesc var4("var4");VarDesc var5("var5");VarDesc var6("var6");ir::Node* add = g->CreateOpNode(&add_op);ir::Node* mul = g->CreateOpNode(&mul_op);ir::Node* relu = g->CreateOpNode(&relu_op);ir::Node* v0 = g->CreateEmptyNode("var0", Node::Type::kVariable);     // 创建空节点用意是?ir::Node* v1 = g->CreateVarNode(&var1);ir::Node* v2 = g->CreateVarNode(&var2);ir::Node* v3 = g->CreateVarNode(&var3);ir::Node* v4 = g->CreateVarNode(&var4);ir::Node* v5 = g->CreateVarNode(&var5);ir::Node* v6 = g->CreateVarNode(&var6);ir::Node* v7 = g->CreateControlDepVar();// fill op nodemul->inputs = {v0, v1, v2};mul->outputs = {v3};add->inputs = {v3, v4};add->outputs = {v5};relu->inputs = {v5};relu->outputs = {v6, v7};// fill variable nodev0->outputs = {mul};v1->outputs = {mul};v2->outputs = {mul};v3->inputs = {mul};v3->outputs = {add};v4->outputs = {add};v5->inputs = {add};v5->outputs = {relu};v6->inputs = {relu};v7->inputs = {relu};return g;
}

上边这个注释有点儿问题:

  // v1 --//      | --> mul --> v3 --// v2 --                   | --> add --> v5 --> relu --> v6//                    v4 --

应该改成:

  // v0 --|// v1 --|                  // v2 --| --> mul  --> v3 --|//                 --> v4 --| --> add  --> v5 --> relu  --> v6//                                                      --> v7

接下来的 TEST 和之前的一样,只不过由于图结构变化,pass 之后图结构都变化为 kCinnLaunchOp

TEST(BuildCinnPassTest, AllOpSupportCinn) {auto g = BuildAllOpSupportCinnGraph();auto pass =paddle::framework::ir::PassRegistry::Instance().Get("build_cinn_pass");pass->Apply(g.get());// After search, the graph should as following// v0 --|// v1 --|                   |--> v6// v2 --| --> kCinnLaunchOp |--> v7// v4 --|const auto& nodes = g->Nodes();ASSERT_EQ(nodes.size(), static_cast<size_t>(7));      // 节点数为 7, 4个输入, 2个输出 和 1 个 Op 节点ASSERT_TRUE(CheckGraphIndependence(nodes));           // 检测该图是否独立,是否会依赖其他图// A new op named kCinnLaunchOp should be addedASSERT_TRUE(CheckNodeExisted(nodes, kCinnLaunchOp));  // kCinnLaunchOp 是个常量字符串, 检测节点 vector 中有无 kCinnLaunchOp auto* cinn_op = GetNode(nodes, kCinnLaunchOp);auto* v0 = GetNode(nodes, "var0");auto* v1 = GetNode(nodes, "var1");                    // 依次获取对应的 var Node 指针auto* v2 = GetNode(nodes, "var2");auto* v4 = GetNode(nodes, "var4");auto* v6 = GetNode(nodes, "var6");auto* v7 = GetNode(nodes, Node::kControlDepVarName);// 查看 cinn_op 的输入输出是否与 `v0, v1, v2, v4` 和 `v6, v7` 对应ASSERT_EQ(std::unordered_set<Node*>(cinn_op->inputs.begin(), cinn_op->inputs.end()),std::unordered_set<Node*>({v0, v1, v2, v4}));ASSERT_EQ(std::unordered_set<Node*>(cinn_op->outputs.begin(),cinn_op->outputs.end()),std::unordered_set<Node*>({v6, v7}));// 查看 var 节点的输入输出是否是 cinn_op ASSERT_EQ(v1->outputs, std::vector<Node*>({cinn_op}));ASSERT_EQ(v6->inputs, std::vector<Node*>({cinn_op}));// previous op (mul, add, relu) should all removed// 由于 mul/elementwise_add/relu 被整体合并为 cinn_op 所以图中不应该被搜索到ASSERT_FALSE(CheckNodeExisted(nodes, "mul"));ASSERT_FALSE(CheckNodeExisted(nodes, "elementwise_add"));ASSERT_FALSE(CheckNodeExisted(nodes, "relu"));// After search, there should has just one cinn subgraph// feed --> v1 --//               | --> mul --> v3 --// feed --> v2 --                   | --> add --> v5 --> relu --> v6 --> fetch//                    feed --> v4 --// 获取编译完毕之后的 key, 之后会根据 key 去取对应的 subgraph auto compilation_keys = GetCompilationKeys(*g);ASSERT_EQ(compilation_keys.size(), static_cast<size_t>(1));  // 因为只有一个 kCinnLaunchOp 所以 key 的数量也为 1 auto* cinn_compiler = CinnCompiler::GetInstance();const auto& subgraph = cinn_compiler->FindGraph(compilation_keys[0]);  // 根据 key 拿对应的子图const auto& subnodes = subgraph.Nodes();             // 拿子图的节点setASSERT_EQ(subnodes.size(), static_cast<size_t>(13));ASSERT_TRUE(CheckGraphIndependence(subnodes));// 该 cinn op 就是这三 mul | elementwise_add | relu 的合体ASSERT_TRUE(CheckNodeExisted(subnodes, "mul"));ASSERT_TRUE(CheckNodeExisted(subnodes, "elementwise_add"));ASSERT_TRUE(CheckNodeExisted(subnodes, "relu"));ASSERT_EQ(CountNode(subnodes, "feed"), 3);   // 上边注释有 3个feed OpASSERT_EQ(CountNode(subnodes, "fetch"), 1);  // 1 个 fetch Op// 在 kCinnLaunchOp 中有参和无参的 node 都应当有 feed Op // No-parameter input should has feed opauto new_v1 = GetNode(subnodes, "var1");ASSERT_EQ(new_v1->inputs.size(), static_cast<size_t>(1));ASSERT_EQ(new_v1->outputs.size(), static_cast<size_t>(1));ASSERT_EQ(new_v1->inputs[0]->Name(), "feed");ASSERT_EQ(new_v1->outputs[0]->Name(), "mul");// Parameter input should also have the feed opauto new_v2 = GetNode(subnodes, "var2");ASSERT_EQ(new_v2->inputs.size(), static_cast<size_t>(1));ASSERT_EQ(new_v2->inputs[0]->Name(), "feed");ASSERT_EQ(new_v2->outputs.size(), static_cast<size_t>(1));ASSERT_EQ(new_v2->outputs[0]->Name(), "mul");// kCinnLaunchOp 输出中应当有 fetch Op// output should has fetch opauto new_v6 = GetNode(subnodes, "var6");ASSERT_EQ(new_v6->inputs.size(), static_cast<size_t>(1));ASSERT_EQ(new_v6->outputs.size(), static_cast<size_t>(1));ASSERT_EQ(new_v6->inputs[0]->Name(), "relu");ASSERT_EQ(new_v6->outputs[0]->Name(), "fetch");
}

第一个单测是只有 fake Op 没办法 pass 优化,第二个单测是所有Op 都支持 CINN Pass, 那下一个就是一半是 fake Op,另一半是 只是 CINN Pass 的 OP

std::unique_ptr<Graph> BuildGraphWithOneCinnSubgraph() {ProgramDesc prog;auto g = std::make_unique<Graph>(prog);// fake1 --> v1 --//                | --> mul --> v3 --> relu --> v4 --> fake2//           v2 --OpDesc fake1_op;fake1_op.SetType("fake1");OpDesc mul_op;mul_op.SetType("mul");OpDesc relu_op;relu_op.SetType("relu");OpDesc fake2_op;fake2_op.SetType("fake2");VarDesc var1("var1");VarDesc var2("var2");var2.SetPersistable(true);var2.SetIsParameter(true);VarDesc var3("var3");VarDesc var4("var4");ir::Node* fake1 = g->CreateOpNode(&fake1_op);ir::Node* mul = g->CreateOpNode(&mul_op);ir::Node* relu = g->CreateOpNode(&relu_op);ir::Node* fake2 = g->CreateOpNode(&fake2_op);ir::Node* v1 = g->CreateVarNode(&var1);ir::Node* v2 = g->CreateVarNode(&var2);ir::Node* v3 = g->CreateVarNode(&var3);ir::Node* v4 = g->CreateVarNode(&var4);// fill op nodefake1->outputs = {v1};mul->inputs = {v2, v1};mul->outputs = {v3};relu->inputs = {v3};relu->outputs = {v4};fake2->inputs = {v4};// fill variable nodev2->outputs = {mul};v1->inputs = {fake1};v1->outputs = {mul};v3->inputs = {mul};v3->outputs = {relu};v4->inputs = {relu};v4->outputs = {fake2};return g;
}

上边的函数就是建立了一个这样的一个图

  // fake1 --> v1 --//                | --> mul --> v3 --> relu --> v4 --> fake2//           v2 --

通过 cinn pass 之后这个图的节点变成下边儿这样:

  // fake1 --> v1 --//                | --> kCinnLaunchOp --> v4 --> fake2//           v2 --

只有一个 kCinnLaunchOp 其子图为,有9个节点

  // feed --> v1 --//               | --> mul --> v3 --> relu --> v4 --> fetch// feed --> v2 --

之前的图是单个 cinn op,下一个单测是多个 cinn op 的情况:

std::unique_ptr<Graph> BuildGraphWithMultiCinnSubgraph() {ProgramDesc prog;auto g = std::make_unique<Graph>(prog);// fake1 --> v1 --//                | --> mul --> v3 --> fake2 --> v4 --> relu --> v5 --> fake3//           v2 --OpDesc fake1_op;fake1_op.SetType("fake1");OpDesc mul_op;mul_op.SetType("mul");OpDesc relu_op;relu_op.SetType("relu");OpDesc fake2_op;fake2_op.SetType("fake2");OpDesc fake3_op;fake3_op.SetType("fake3");VarDesc var1("var1");VarDesc var2("var2");var2.SetPersistable(true);var2.SetIsParameter(true);VarDesc var3("var3");VarDesc var4("var4");VarDesc var5("var5");ir::Node* fake1 = g->CreateOpNode(&fake1_op);ir::Node* mul = g->CreateOpNode(&mul_op);ir::Node* relu = g->CreateOpNode(&relu_op);ir::Node* fake2 = g->CreateOpNode(&fake2_op);ir::Node* fake3 = g->CreateOpNode(&fake3_op);ir::Node* v1 = g->CreateVarNode(&var1);ir::Node* v2 = g->CreateVarNode(&var2);ir::Node* v3 = g->CreateVarNode(&var3);ir::Node* v4 = g->CreateVarNode(&var4);ir::Node* v5 = g->CreateVarNode(&var5);// fill op nodefake1->outputs = {v1};mul->inputs = {v2, v1};mul->outputs = {v3};fake2->inputs = {v3};fake2->outputs = {v4};relu->inputs = {v4};relu->outputs = {v5};fake3->inputs = {v5};// fill variable nodev2->outputs = {mul};v1->inputs = {fake1};v1->outputs = {mul};v3->inputs = {mul};v3->outputs = {fake2};v4->inputs = {fake2};v4->outputs = {relu};v5->inputs = {relu};v5->outputs = {fake3};return g;
}

以上代码建立一个这样的图:

  // fake1 --> v1 --//                | --> mul --> v3 --> fake2 --> v4 --> relu --> v5 --> fake3//           v2 --

fake2 op 为界,可以建立两个 cinn op pass

  // fake1 -> v1 -//              | -> CinnOp -> v3 -> fake2 -> v4 -> CinnOp ->v5 -> fake3//          v2 -

cinn pass 就两句代码:

  auto pass =paddle::framework::ir::PassRegistry::Instance().Get("build_cinn_pass");pass->Apply(g.get());

此处是检验有两个 cinn pass Op 的代码:

  // A new op named kCinnLaunchOp should be addedASSERT_TRUE(CheckNodeExisted(nodes, kCinnLaunchOp));ASSERT_EQ(CountNode(nodes, kCinnLaunchOp), 2);

最后的编译结果是 cinn pass 之后有两个 子图:

  // subgraph1:// feed --> v4 --> relu --> v5 --> fetch// subgraph2:// feed --> v1 --//               | --> mul --> v3 --> fetch//          v2 --

BuildGraphWithNoNeedBufferInput 就是建立一个这样的子图:

  // fake1 --> v1 --                 --> v4 --> relu_grad --> v6//           v2 -- | --> add_grad |//           v3 --                 --> v5 --> fake2

BuildGraphWithNoNeedBufferInput 与之前不同的是,add_grad_op 使用了设置输入的 API SetInput

  OpDesc add_grad_op;add_grad_op.SetType("elementwise_add_grad");add_grad_op.SetInput(::paddle::framework::GradVarName("Out"), {"var1"});add_grad_op.SetInput("X", {"var2"});add_grad_op.SetInput("Y", {"var3"});

之后的单测写了,no_need_buffer_x 不知道什么意思.

  // A new op named kCinnLaunchOp should be added and// its input arguments are set correctlyASSERT_TRUE(CheckNodeExisted(nodes, kCinnLaunchOp));ASSERT_EQ(CountNode(nodes, kCinnLaunchOp), 1);auto* cinn_op_node = GetNode(nodes, kCinnLaunchOp);ASSERT_EQ(cinn_op_node->Op()->Input(operators::kX),std::vector<std::string>({"var1"}));auto& no_need_buffer_x = cinn_op_node->Op()->Input(operators::kNoNeedBufferX);ASSERT_EQ(std::unordered_set<std::string>(no_need_buffer_x.begin(),no_need_buffer_x.end()),std::unordered_set<std::string>({"var2", "var3"}));

这里的 no_need_buffer_feeds 什么意思??

  ASSERT_TRUE(CheckNodeExisted(subnodes, "elementwise_add_grad"));ASSERT_TRUE(CheckNodeExisted(subnodes, "relu_grad"));ASSERT_EQ(CountNode(subnodes, "feed"), 3);ASSERT_EQ(CountNode(subnodes, "fetch"), 2);const auto& no_need_buffer_feeds =subgraph.Get<std::unordered_set<std::string>>(kNoNeedBufferFeeds);ASSERT_EQ(no_need_buffer_feeds.size(), 2);ASSERT_EQ(no_need_buffer_feeds,std::unordered_set<std::string>({"var2", "var3"}));// check the attributes of variable lists are saved correctlyASSERT_TRUE(subgraph.Has(kInputVars));EXPECT_EQ(subgraph.Get<std::vector<std::string>>(kInputVars),std::vector<std::string>({"var1"}));ASSERT_TRUE(subgraph.Has(kInternalVars));EXPECT_EQ(subgraph.Get<std::vector<std::string>>(kInternalVars),std::vector<std::string>({"var4"}));ASSERT_TRUE(subgraph.Has(kOutputVars));const auto& output_vars = subgraph.Get<std::vector<std::string>>(kOutputVars);EXPECT_EQ(std::unordered_set<std::string>(output_vars.begin(), output_vars.end()),std::unordered_set<std::string>({"var5", "var6"}));
TEST(BuildCinnPassTest, TestSkipGcVars){auto g = BuildGraphWithOneCinnSubgraph();// 这里什么意思????std::unordered_set<std::string> all_skip_gc_vars = {"var1", "var3"};g->SetNotOwned(kSkipGcVarNames, &all_skip_gc_vars);auto pass =paddle::framework::ir::PassRegistry::Instance().Get("build_cinn_pass");pass->Apply(g.get());// After search, the graph should as following// fake1 --> v1 --//                | --> kCinnLaunchOp --> v4 --> fake2//           v2 --const auto& nodes = g->Nodes();ASSERT_EQ(nodes.size(), static_cast<size_t>(7));  // 这里为啥变成了 7ASSERT_TRUE(CheckGraphIndependence(nodes));// A new op named kCinnLaunchOp should be addedASSERT_TRUE(CheckNodeExisted(nodes, kCinnLaunchOp));// After search, there should has just one cinn subgraph// Note v3 has fetched because of v3 in kSkipGcVarNames// And v1 is a feed var so v1 no need fetched though it in kSkipGcVarNames// feed --> v1 --//               | --> mul --> v3 --> relu --> v4 --> fetch// feed --> v2 --                 --> fetchauto compilation_keys = GetCompilationKeys(*g);ASSERT_EQ(compilation_keys.size(), static_cast<size_t>(1));auto* cinn_compiler = CinnCompiler::GetInstance();const auto& subgraph = cinn_compiler->FindGraph(compilation_keys[0]);const auto& subnodes = subgraph.Nodes();ASSERT_EQ(subnodes.size(), static_cast<size_t>(10));ASSERT_TRUE(CheckGraphIndependence(subnodes));ASSERT_EQ(CountNode(subnodes, "feed"), 2);// var3 and var4 should has fetch opASSERT_EQ(CountNode(subnodes, "fetch"), 2);
}

最后两个 TEST 没看懂,留下问题

相关文章:

Paddle build_cinn_pass_test源码阅读(fluid目录下)

代码位置在 paddle\fluid\framework\paddle2cinn\build_cinn_pass_test.cc &#xff0c;因为paddle CINN和PIR部分依旧在高频更新&#xff0c;所以各位看到的可能和我的不一样 inline bool CheckNodeExisted(const std::unordered_set<Node*>& nodes,const std::str…...

函数调用:为什么会发生stack overflow?

在开发软件的过程中我们经常会遇到错误&#xff0c;如果你用 Google 搜过出错信息&#xff0c;那你多少应该都访问过Stack Overflow这个网站。作为全球最大的程序员问答网站&#xff0c;Stack Overflow 的名字来自于一个常见的报错&#xff0c;就是栈溢出&#xff08;stack ove…...

git log

git log -p 是一个用于显示git commit历史的命令&#xff0c;它会展示每个commit的详细信息&#xff0c;包括每个修改文件的清单、添加/删除的行所在的位置以及具体的实际更改。这个命令能够让用户深入了解仓库的历史记录。 与git log相比&#xff0c;git log -p 提供了更多的…...

在面试提问环节应该问那些内容

在面试提问环节应该问那些内容 薪资和福利&#xff1a; 你可以询问关于薪资、福利和其他福利待遇的细节&#xff0c;包括工资结构、健康保险、退休计划、带薪休假等。 了解关于加班、绩效奖金和涨薪机会的信息。 工作时间和灵活性&#xff1a; 询问工作时间、工作日和工作日…...

【vb.net】轻量JSON序列及反序列化

这个代码写的有点时间了&#xff0c;可能有点小bug&#xff0c;欢迎评论区反馈 作用是将Json文本转化成一个HarryNode类进行相关的Json对象处理或者读取&#xff0c;也可以将一个HarryNode对象用ToString变为Json文本。 举例&#xff1a; 1、读取节点数据 dim harryNode N…...

【Vue】vue2与netcore webapi跨越问题解决

系列文章 C#底层库–记录日志帮助类 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/124187709 文章目录 系列文章前言一、技术介绍二、问题描述三、问题解决3.1 方法一&#xff1a;前端Vue修改3.2 方法二&#xff1a;后端允许Cors跨越访问 四、资源…...

SpringSecurity + jwt + vue2 实现权限管理 , 前端Cookie.set() 设置jwt token无效问题(已解决)

问题描述 今天也是日常写程序的一天 , 还是那个熟悉的IDEA , 还是那个熟悉的Chrome浏览器 , 还是那个熟悉的网站 , 当我准备登录系统进行登录的时候 , 发现会直接重定向到登录页 , 后端也没有报错 , 前端也没有报错 , 于是我得脸上又多了一张痛苦面具 , 紧接着在前端疯狂debug…...

【21】c++设计模式——>装饰模式

装饰模式的定义 装饰模式也可以称为封装模式&#xff0c;所谓的封装就是在原有行为之上进行扩展&#xff0c;并不会改变该行为&#xff1b; 例如网络通信&#xff1a; 在进行网络通信的时候&#xff0c;数据是基于IOS七层或四层网络模型&#xff08;某些层合并之后就是四层模型…...

【博客707】模版化拆解并获取victoriametrics的metricsql各个元素

golang解析victoriametrics的metricsql 场景&#xff1a; 需要拆解metricsql中的部分元素&#xff0c;比如&#xff1a;rollup function&#xff0c;label filter等需要对语法合法性进行判断&#xff0c;同时拒绝某些查询函数我们需要拆解metricsql并进行改造 使用victoriam…...

nodejs + express 实现 http文件下载服务程序

nodejs express 实现 http文件下载服务程序&#xff0c; 主要包括两个功能&#xff1a;指定目录的文件列表&#xff0c;某个文件的下载。 假设已经安装好 nodejs ; cd /js/node_js ; 安装在当前目录的 node_modules/ npm install express --save npm install express-gene…...

Qt多文本编辑器项目实战

0x00 引言 本文将详细讲解如何使用Qt实现一个多文本编辑器。涉及的话题包括&#xff1a;Qt框架基础、窗体布局、文本编辑、拓展功能等等。 在阅读本文之前&#xff0c;你需要掌握基本的C编程知识和Qt框架的使用方法。 0x01 新建Qt项目 在Qt Creator中&#xff0c;新建一个Q…...

CVE-2017-7529 Nginx越界读取内存漏洞

漏洞概述 当使用Nginx标准模块时&#xff0c;攻击者可以通过发送包含恶意构造range域的header请求&#xff0c;来获取响应中的缓存文件头部信息。在某些配置中&#xff0c;缓存文件头可能包含后端服务器的IP地址或其它敏感信息&#xff0c;从而导致信息泄露。 影响版本 Ngin…...

力扣每日一题136:只出现一次的数字

题目描述&#xff1a; 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 示例 1 &#…...

导航栏参考代码

导航栏参考代码 <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>导航栏参考代码</title> </head> <body> <table width"858" border"0" align"center"><tr&g…...

区块链(11):java区块链项目之页面部分实现

addPeer.html <!DOCTYPE html> <html> <head><meta charset="utf-8"> <title>java区块链</title><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="styles…...

RootSIFT---SIFT图像特征的扩展

RootSIFT是论文 Three things everyone should know to improve object retrieval - 2012所提出的 A Comparative Analysis of RootSIFT and SIFT Methods for Drowsy Features Extraction - 2020 当比较直方图时&#xff0c;使用欧氏距离通常比卡方距离或Hellinger核时的性能…...

ChatGPT角色扮演教程,Prompt词分享

使用指南 1、可直复制使用 2、可以前往已经添加好Prompt预设的AI系统测试使用 https://ai.idcyli.comhttps://ai.idcyli.com 雅思写作考官 我希望你假定自己是雅思写作考官&#xff0c;根据雅思评判标准&#xff0c;按我给你的雅思考题和对应答案给我评分&#xff0c;并且按…...

zabbix监控——自定义监控内容

目录 自定义监控项步骤 案例 1、明确需要执行的命令 2、创建 zabbix 的监控项配置文件&#xff0c;用于自定义 key&#xff0c;并重启zabbix-agent2 3、.在服务端验证新建的监控项 4、在 Web 页面创建自定义监控项模板 1&#xff09;创建模板 2&#xff09;创建监控项 …...

中断机制-中断协商机制、中断方法

4.1 线程中断机制 4.1.1 从阿里蚂蚁金服面试题讲起 Java.lang.Thread下的三个方法: 4.1.2 什么是中断机制 首先&#xff0c;一个线程不应该由其他线程来强制中断或停止&#xff0c;而是应该由线程自己自行停止&#xff0c;自己来决定自己的命运&#xff0c;所以&#xff0c;…...

three.js入门 —— 实现第一个3D案例

前言&#xff1a; three.js入门&#xff0c;根据文档实现第一个3D案例 效果图&#xff1a; 代码实现&#xff1a; const scene new THREE.Scene();//创建一个长方体几何对象Geometryconst geometry new THREE.BoxGeometry(100, 100, 100);//创建一个网络基础材质的材质对象…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...