C#编译器和优化会破坏这个代码吗

本文关键字:代码 编译器 优化 | 更新日期: 2023-09-27 18:29:11

给定函数内的以下C#代码:

....
var documentCollection =
    client.CreateDocumentCollectionQuery("dbs/" + database.Id)
        .Where(c => c.Id == DocumentCollectionName)
        .AsEnumerable()
        .FirstOrDefault();
if (documentCollection == null)
{
    documentCollection =
        await
        client.CreateDocumentCollectionAsync(
            "dbs/" + database.Id,
            new DocumentCollection { Id = DocumentCollectionName });
}
return client;

注意:我不是返回documentCollection,我只需要初始化它,如果还没有的话(CreateDocumentCollectionAsync调用)。因此,在if块之后,documentCollection成为未使用的变量。

现在-ReSharper建议将其优化为:

var documentCollection =
    client.CreateDocumentCollectionQuery("dbs/" + database.Id)
        .Where(c => c.Id == DocumentCollectionName)
        .AsEnumerable()
        .FirstOrDefault()
    ?? await
        client.CreateDocumentCollectionAsync(
            "dbs/" + database.Id,
            new DocumentCollection { Id = DocumentCollectionName });

并且现在指示documentCollection是未使用的变量。

我的问题是:C#代码优化或"发布"构建会完全删除这行代码并导致CreateDocumentCollectionAsync永远不会启动吗?

C#优化课程告诉我,"发布"会在函数中不需要垃圾收集变量时立即构建它们,而调试构建则不会这样做(出于调试目的)。

我现在想知道它是否如此急切,甚至优化了一个未使用的变量赋值(这会在后台触发一个操作)。

C#编译器和优化会破坏这个代码吗

不,编译器和JIT都不会优化方法调用。

下面列出了JIT编译器的作用。例如,它确实优化了if (false) { ... }块或未使用的变量分配。它不仅优化了您的方法调用。如果这是真的,那么对void方法的每次调用都应该取消。

任何优化器都只能删除没有可观察行为的代码。

否则,它就不是一个优化器。

免责声明:这是一个可能会发生变化的实现细节,对此持保留态度。


CLI规范的ECMA-335第I.12.6.4节(优化)规定如下:

CLI的一致性实现可以自由执行程序使用任何技术,保证执行,线程生成的副作用和异常按CIL指定的顺序可见。仅用于此目的易失性操作(包括易失性读取)构成可见副作用。(请注意,虽然只有波动性操作才构成可见的副作用,不稳定的操作也会影响可见性非易失性引用。)易失性操作在§I.12.6.7.没有与例外情况相关的订购保证由另一个线程注入到一个线程中(此类异常包括有时被称为"异步异常"(例如。,System.Threading.ThreadAbortException).

[理由:优化编译器可以自由地将副作用和同步异常重新排序到这种重新排序不会改变任何可观察程序的程度行为最终原理]

[注:允许CLI的实现使用优化编译器,例如,将CIL转换为提供的本地机器代码编译器(在执行的每个线程内)保持相同副作用和同步异常的顺序这是更强的条件比ISO C++(允许在一对序列点)或ISO方案(允许重新排序参数到函数)。尾注]

这意味着,任何符合CLI的实现都可以自由地进行此类优化,如果它可以保证副作用的顺序不会受到损害这意味着,如果一个方法没有副作用,并且JIT或语言编译器静态分析给定事实的副作用,则可以对其进行优化,因为无论是否使用该方法,都不会对所述副作用进行重新排序。

也就是说,目前,C#编译器优化未使用的变量,但不优化方法调用。编译器没有对整个方法调用进行静态分析,因此它不能"证明";该方法在您的代码中没有副作用。更重要的是,JIT优化没有那么激进,它可能只内联方法调用,但不会对其进行优化。

作为开源,您可以看到x86 JIT编译阶段,并了解正在进行的一些优化(通过compphases.h):

// Names of x86 JIT phases, in order.  Assumes that the caller defines CompPhaseNameMacro
// in a useful way before including this file, e.g., to define the phase enumeration and the
// corresponding array of string names of those phases.  This include file undefines CompPhaseNameMacro
// after the last use.
// The arguments are:
//   CompPhaseNameMacro(enumName, stringName, hasChildren, parent)
//     "enumName" is an Enumeration-style all-caps name.
//     "stringName" is a self-explanatory.
//     "hasChildren" is true if this phase is broken out into subphases.
//         (We should never do EndPhase on a phase that has children, only on 'leaf phases.')
//     "parent" is -1 for leaf phases, otherwise it is the "enumName" of the parent phase.
CompPhaseNameMacro(PHASE_PRE_IMPORT,             "Pre-import",                     "PRE-IMP",  false, -1)
CompPhaseNameMacro(PHASE_IMPORTATION,            "Importation",                    "IMPORT",   false, -1)
CompPhaseNameMacro(PHASE_POST_IMPORT,            "Post-import",                    "POST-IMP", false, -1)
CompPhaseNameMacro(PHASE_MORPH,                  "Morph",                          "MORPH",    false, -1)
CompPhaseNameMacro(PHASE_GS_COOKIE,              "GS Cookie",                      "GS-COOK",  false, -1)
CompPhaseNameMacro(PHASE_COMPUTE_PREDS,          "Compute preds",                  "PREDS",    false, -1)
CompPhaseNameMacro(PHASE_MARK_GC_POLL_BLOCKS,    "Mark GC poll blocks",            "GC-POLL",  false, -1)
CompPhaseNameMacro(PHASE_COMPUTE_EDGE_WEIGHTS,   "Compute edge weights (1)",       "EDG-WGT",  false, -1)
#if FEATURE_EH_FUNCLETS
CompPhaseNameMacro(PHASE_CREATE_FUNCLETS,        "Create EH funclets",             "EH-FUNC",  false, -1)
#endif // FEATURE_EH_FUNCLETS
CompPhaseNameMacro(PHASE_OPTIMIZE_LAYOUT,        "Optimize layout",                "LAYOUT",   false, -1)
CompPhaseNameMacro(PHASE_OPTIMIZE_LOOPS,         "Optimize loops",                 "LOOP-OPT", false, -1)
CompPhaseNameMacro(PHASE_CLONE_LOOPS,            "Clone loops",                    "LP-CLONE", false, -1)
CompPhaseNameMacro(PHASE_UNROLL_LOOPS,           "Unroll loops",                   "UNROLL",   false, -1)
CompPhaseNameMacro(PHASE_HOIST_LOOP_CODE,        "Hoist loop code",                "LP-HOIST", false, -1)
CompPhaseNameMacro(PHASE_MARK_LOCAL_VARS,        "Mark local vars",                "MARK-LCL", false, -1)
CompPhaseNameMacro(PHASE_OPTIMIZE_BOOLS,         "Optimize bools",                 "OPT-BOOL", false, -1)
CompPhaseNameMacro(PHASE_FIND_OPER_ORDER,        "Find oper order",                "OPER-ORD", false, -1)
CompPhaseNameMacro(PHASE_SET_BLOCK_ORDER,        "Set block order",                "BLK-ORD",  false, -1)
CompPhaseNameMacro(PHASE_BUILD_SSA,              "Build SSA representation",       "SSA",      true,  -1)
CompPhaseNameMacro(PHASE_BUILD_SSA_TOPOSORT,     "SSA: topological sort",          "SSA-SORT", false, PHASE_BUILD_SSA)
CompPhaseNameMacro(PHASE_BUILD_SSA_DOMS,         "SSA: Doms1",                     "SSA-DOMS", false, PHASE_BUILD_SSA)
CompPhaseNameMacro(PHASE_BUILD_SSA_LIVENESS,     "SSA: liveness",                  "SSA-LIVE", false, PHASE_BUILD_SSA)
CompPhaseNameMacro(PHASE_BUILD_SSA_IDF,          "SSA: IDF",                       "SSA-IDF",  false, PHASE_BUILD_SSA)
CompPhaseNameMacro(PHASE_BUILD_SSA_INSERT_PHIS,  "SSA: insert phis",               "SSA-PHI",  false, PHASE_BUILD_SSA)
CompPhaseNameMacro(PHASE_BUILD_SSA_RENAME,       "SSA: rename",                    "SSA-REN",  false, PHASE_BUILD_SSA)
CompPhaseNameMacro(PHASE_EARLY_PROP,             "Early Value Propagation",        "ERL-PROP", false, -1)
CompPhaseNameMacro(PHASE_VALUE_NUMBER,           "Do value numbering",             "VAL-NUM",  false, -1)
CompPhaseNameMacro(PHASE_OPTIMIZE_INDEX_CHECKS,  "Optimize index checks",          "OPT-CHK",  false, -1)
#if FEATURE_VALNUM_CSE
CompPhaseNameMacro(PHASE_OPTIMIZE_VALNUM_CSES,   "Optimize Valnum CSEs",           "OPT-CSE",  false, -1)
#endif  
CompPhaseNameMacro(PHASE_VN_COPY_PROP,           "VN based copy prop",             "CP-PROP",  false, -1)
#if ASSERTION_PROP
CompPhaseNameMacro(PHASE_ASSERTION_PROP_MAIN,    "Assertion prop",                 "AST-PROP", false, -1)
#endif
CompPhaseNameMacro(PHASE_UPDATE_FLOW_GRAPH,      "Update flow graph",              "UPD-FG",   false, -1)
CompPhaseNameMacro(PHASE_COMPUTE_EDGE_WEIGHTS2,  "Compute edge weights (2)",       "EDG-WGT2", false, -1)
CompPhaseNameMacro(PHASE_DETERMINE_FIRST_COLD_BLOCK, "Determine first cold block", "COLD-BLK", false, -1)
CompPhaseNameMacro(PHASE_RATIONALIZE,            "Rationalize IR",                 "RAT",      false, -1)
CompPhaseNameMacro(PHASE_SIMPLE_LOWERING,        "Do 'simple' lowering",           "SMP-LWR",  false, -1)
CompPhaseNameMacro(PHASE_LCLVARLIVENESS,         "Local var liveness",             "LIVENESS", true, -1)
CompPhaseNameMacro(PHASE_LCLVARLIVENESS_INIT,    "Local var liveness init",        "LIV-INIT", false, PHASE_LCLVARLIVENESS)
CompPhaseNameMacro(PHASE_LCLVARLIVENESS_PERBLOCK,"Per block local var liveness",   "LIV-BLK",  false, PHASE_LCLVARLIVENESS)
CompPhaseNameMacro(PHASE_LCLVARLIVENESS_INTERBLOCK,  "Global local var liveness",  "LIV-GLBL", false, PHASE_LCLVARLIVENESS)
CompPhaseNameMacro(PHASE_LVA_ADJUST_REF_COUNTS,  "LVA adjust ref counts",          "REF-CNT",  false, -1)
#ifdef LEGACY_BACKEND
CompPhaseNameMacro(PHASE_RA_ASSIGN_VARS,         "RA assign vars",                 "REGALLOC", false, -1)
#endif // LEGACY_BACKEND
CompPhaseNameMacro(PHASE_LOWERING_DECOMP,        "Lowering decomposition",         "LWR-DEC",  false, -1)
CompPhaseNameMacro(PHASE_LOWERING,               "Lowering nodeinfo",              "LWR-INFO", false, -1)
#ifndef LEGACY_BACKEND
CompPhaseNameMacro(PHASE_LINEAR_SCAN,            "Linear scan register alloc",     "LSRA",     true, -1)
CompPhaseNameMacro(PHASE_LINEAR_SCAN_BUILD,      "LSRA build intervals",           "LSRA-BLD", false, PHASE_LINEAR_SCAN)
CompPhaseNameMacro(PHASE_LINEAR_SCAN_ALLOC,      "LSRA allocate",                  "LSRA-ALL", false, PHASE_LINEAR_SCAN)
CompPhaseNameMacro(PHASE_LINEAR_SCAN_RESOLVE,    "LSRA resolve",                   "LSRA-RES", false, PHASE_LINEAR_SCAN)
#endif // !LEGACY_BACKEND
CompPhaseNameMacro(PHASE_GENERATE_CODE,          "Generate code",                  "CODEGEN",  false, -1)
CompPhaseNameMacro(PHASE_EMIT_CODE,              "Emit code",                      "EMIT",     false, -1)
CompPhaseNameMacro(PHASE_EMIT_GCEH,              "Emit GC+EH tables",              "EMT-GCEH", false, -1)

一些优化是:

  • 死码消除
  • 线性扫描寄存器分配
  • 循环展开
  • 范围检查消除

这篇文章继续描述JIT所做的一些优化,@EricLippert在这里谈论优化时给出了一个很好的答案