This is an archive of the discontinued LLVM Phabricator instance.

[LoopInterchange] Correcting the profitability check
ClosedPublic

Authored by ram-NK on Oct 12 2022, 12:40 PM.

Details

Summary

If inner loop is loop independent or doesn't carry any dependency then, loop interchange is not profitable. Also if outer loop is not loop independent then, loop interchange is not profitable. if inner loop has dependence and outer loop is loop independent then, it is profitable to interchange to enable inner loop parallelism. Corrected the dependency checking inside isProfitableForVectorization(). Also addresses the endless interchange problem. If Cache analysis could decide the loop order then, isProfitable will not invoke isProfitableForVectorization for decidinding better loop interchange.

Diff Detail

Event Timeline

ram-NK created this revision.Oct 12 2022, 12:40 PM
Herald added a project: Restricted Project. · View Herald TranscriptOct 12 2022, 12:40 PM
Herald added a subscriber: hiraditya. · View Herald Transcript
ram-NK requested review of this revision.Oct 12 2022, 12:40 PM
ram-NK edited the summary of this revision. (Show Details)Oct 12 2022, 12:51 PM
ram-NK added a reviewer: bmahjour.
ram-NK added a comment.EditedOct 16 2022, 8:05 PM

Before this patch, If outer loop dependency direction "=" and Inner loop dependency direction is "S" and "I" then, loop interchange is considered as profitable. Only two cases of dependency is profitable. But for vectorization, ">" and "<" dependency in outer loop is more profitable when interchanged. After patch [=,<] and [=,>] will be interchanged for vectorization.

bmahjour added a project: Restricted Project.Oct 17 2022, 6:10 AM

The proper profitability analysis for loop interchange uses CacheCost, although there are cases where it may be unable to analyze certain accesses (eg due to delinearization issues). It would be good to understand why we don't go through the CacheCost path in your use case.

The legacy heuristic doesn't look right to me, although your changes make the logic more aligned with the comments in the code.

llvm/test/Transforms/LoopInterchange/pr43797-lcssa-for-multiple-outer-loop-blocks.ll
11 ↗(On Diff #467235)

I'm worried about losing functional coverage by avoiding interchange here due to profitability. Can you play with -loop-interchange-threshold or make slight changes to the memory accesses to make the test case profitable?

If outer loop dependency direction "=" and Inner loop dependency direction is "S" and "I" then, loop interchange is considered as profitable. Only two cases of dependency is profitable. But for vectorization, ">" and "<" dependency in outer loop is more profitable when interchanged. After patch [=,<] and [=,>] will be interchanged for vectorization.

I thought what you meant is that after this patch, [<, =] and [>, =] (not [=,<] and [=,>]) will be interchanged? Because after interchange the dependency vector would become [=, <] and [=, >] respectively, which could improve potential parallelization and enable finer-grained parallelism, i.e., outer loop parallelism instead of inner loop parallelism. I think this is what isProfitableForVectorization() is supposed to be.

I wonder if it makes sense to you @bmahjour ?

If outer loop dependency direction "=" and Inner loop dependency direction is "S" and "I" then, loop interchange is considered as profitable. Only two cases of dependency is profitable. But for vectorization, ">" and "<" dependency in outer loop is more profitable when interchanged. After patch [=,<] and [=,>] will be interchanged for vectorization.

I thought what you meant is that after this patch, [<, =] and [>, =] (not [=,<] and [=,>]) will be interchanged? Because after interchange the dependency vector would become [=, <] and [=, >] respectively, which could improve potential parallelization and enable finer-grained parallelism, i.e., outer loop parallelism instead of inner loop parallelism. I think this is what isProfitableForVectorization() is supposed to be.

I wonder if it makes sense to you @bmahjour ?

I think profitability determination solely based on dependency matrix is fundamentally flawed. One obvious example is this

void f1() {
  for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++) {
      B[j][i] = A[j][i];
    }  
}

Assuming A and B don't alias, there are no dependencies, nevertheless interchange is profitable.

Another example is:

void f2() {
  // > =
  for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++) {
      ... = A[i][j];
      A[i-1][j] = ...;
    }
}

The dependence is carried by the outer loop, yet it's not profitable to interchange (since it would make both locality and parallelism worse).

The following example is profitable to interchange, but won't be recognized as profitable after this patch:

void f3() {
  // = =
  for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++) {
      ... = A[j][i];
      A[j][i] = ...;
    }
}
ram-NK updated this revision to Diff 468607.Oct 18 2022, 10:24 AM

I'm worried about losing functional coverage by avoiding interchange here due to profitability. Can you play with -loop-interchange-threshold or make slight changes to the memory accesses to make the test case profitable?

Reverted the changes in pr43797-lcssa-for-multiple-outer-loop-blocks.ll. In this case, CostMap is empty and calculated legacy cost is 0. For retaining the loop interchange functionality, lowered the loop interchange threshold to -1000 (added -loop-interchange-threshold=-1000).

If outer loop dependency direction "=" and Inner loop dependency direction is "S" and "I" then, loop interchange is considered as profitable. Only two cases of dependency is profitable. But for vectorization, ">" and "<" dependency in outer loop is more profitable when interchanged. After patch [=,<] and [=,>] will be interchanged for vectorization.

I thought what you meant is that after this patch, [<, =] and [>, =] (not [=,<] and [=,>]) will be interchanged? Because after interchange the dependency vector would become [=, <] and [=, >] respectively, which could improve potential parallelization and enable finer-grained parallelism, i.e., outer loop parallelism instead of inner loop parallelism. I think this is what isProfitableForVectorization() is supposed to be.

I wonder if it makes sense to you @bmahjour ?

I think profitability determination solely based on dependency matrix is fundamentally flawed. One obvious example is this

void f1() {
  for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++) {
      B[j][i] = A[j][i];
    }  
}

Assuming A and B don't alias, there are no dependencies, nevertheless interchange is profitable.

Another example is:

void f2() {
  // > =
  for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++) {
      ... = A[i][j];
      A[i-1][j] = ...;
    }
}

The dependence is carried by the outer loop, yet it's not profitable to interchange (since it would make both locality and parallelism worse).

The following example is profitable to interchange, but won't be recognized as profitable after this patch:

void f3() {
  // = =
  for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++) {
      ... = A[j][i];
      A[j][i] = ...;
    }
}

In loop interchange, profitablility is determined first by the cost model and then by isProfitableForVectorization(). Loops in f1() and f3() will be interchanged by CacheCost checking. In profitability checking of loop interchange, CacheCost analysis is done first. So this patch will not affect the f1() and f3() cases.

For f2() case, CacheCost and Legacy cost model will not show profitability. If loops in f2() is loop interchanged then, Outer loop parallelization is possible, which could improve parallelism.

Could you add a test case that is not considered profitable where before it was not?

I think profitability determination solely based on dependency matrix is fundamentally flawed.

I understand the idea is that when the locality model says that interchanged and non-interchanged have the same profitability according to cache locality analysis, only then we fall back to a heuristic that determines whether maybe the innermost loop of the interchange loop nest can be vectorized when the one before vectorization could not.

Unfortunately LoopInterchangeProfitability::isProfitable is not structured like that. It falls back to isProfitableForVectorization whenever cache analysis does not want to interchange the loops. This includes the case where the old loop nest has better locality than the interchanged would have. This could result in a endless application of LoopInterchange if we would run it until no more change is to be made:

  1. Cache analysis thinks non-interchanged nest has more locality => fall back to isProfitableForVectorization which returns true => do the interchange
  2. Cache analysis thinks the interchange has more locality => do the interchange (with two interchanges we arrive at the original loop)
  3. Cache analysis thinks non-interchanged nest has more locality => fall back to isProfitableForVectorization which returns true => do the interchange again
  4. ...

I thought what you meant is that after this patch, [<, =] and [>, =] (not [=,<] and [=,>]) will be interchanged? Because after interchange the dependency vector would become [=, <] and [=, >] respectively, which could improve potential parallelization and enable finer-grained parallelism, i.e., outer loop parallelism instead of inner loop parallelism. I think this is what isProfitableForVectorization() is supposed to be.

The current LoopVectorize pass only supports innermost loops, hence you would want the dependencies carried by the outer loops so the LoopVectorize pass does not have to consider them.

Btw, why does LopInterchange use a DepMatrix when the dependencies for all instructions in the loop could be just summarized in a single vector?

Meinersbur added inline comments.Oct 19 2022, 12:20 AM
llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1115–1116

Does you patch cover this TODO?

1116–1124

Shouldn't the condition on Row[InnerLoopId] and Row[OuterLoopId] be exact opposite? That is, it is profitable if the innermost loop has loop-carried dependencies while the outer has not?

1163–1164

Should this only be considered if InnerLoopId is actually an innermost loop (The only kind LoopVectorize can currently process)?

ram-NK updated this revision to Diff 469339.Oct 20 2022, 1:47 PM
ram-NK marked an inline comment as done.
ram-NK edited the summary of this revision. (Show Details)
ram-NK marked 2 inline comments as done.Oct 20 2022, 2:01 PM
ram-NK added inline comments.
llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1115–1116

Corrected the dependency checking and corrected the comments.

1116–1124

Corrected the dependency check. If inner loop has loop carried dependency and outer loop is loop independent then, loop interchange is considered as profitable for vectorization.

ram-NK marked 2 inline comments as done.Oct 20 2022, 2:04 PM
ram-NK added inline comments.
llvm/test/Transforms/LoopInterchange/pr43797-lcssa-for-multiple-outer-loop-blocks.ll
11 ↗(On Diff #467235)

These changes are not needed after correcting the dependency check in loop interchange.

ram-NK added a comment.EditedOct 20 2022, 2:11 PM

Following changes are made as per the comments. If inner loop is loop independent or doesn't carry any dependency then, loop interchange is not considered as profitable. Also if outer loop is not loop independent then, loop interchange is not considered as profitable. If inner loop has dependence and outer loop is loop independent then, it is profitable to interchange to enable inner loop parallelism.

ram-NK edited the summary of this revision. (Show Details)Oct 20 2022, 3:12 PM
ram-NK updated this revision to Diff 469613.Oct 21 2022, 8:05 AM
ram-NK added a comment.EditedOct 28 2022, 8:36 AM

@Meinersbur and @bmahjour, Could you get the chance to review the code I modified as per your comment?

This looks better now, but the problem of "endless interchange" is still not addressed. Per discussion in the loop opt call, we should only use CacheCost when it was able to calculate a valid cost and the loops can be sorted based on "strictly less than" ordering relationship. Only when CacheCost result is indeterminate (eg. two loops have equal cost) or when it is unable to calculate the cost (eg. due to delinearization issues), we should fall back to the isProfitableForVectorization() function.

llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1122

not not -> not

ram-NK updated this revision to Diff 473714.Nov 7 2022, 9:31 AM

This looks better now, but the problem of "endless interchange" is still not addressed. Per discussion in the loop opt call, we should only use CacheCost when it was able to calculate a valid cost and the loops can be sorted based on "strictly less than" ordering relationship. Only when CacheCost result is indeterminate (eg. two loops have equal cost) or when it is unable to calculate the cost (eg. due to delinearization issues), we should fall back to the isProfitableForVectorization() function.

Corrected the "endless interchange" possibility. Now isProfitableForVectorization() only invokes when two loops have equal cost or it is unable to calculate the cost.

ram-NK marked an inline comment as done.Nov 7 2022, 9:31 AM
ram-NK added inline comments.
llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1122

corrected

congzhe added inline comments.Nov 7 2022, 9:03 PM
llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1168
ram-NK updated this revision to Diff 474010.Nov 8 2022, 7:54 AM
ram-NK marked an inline comment as done.
ram-NK marked 2 inline comments as done.Nov 8 2022, 8:08 AM

@congzhe and @bmahjour comments are addressed.

llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1168

Corrected the condition. any of the loop is failed to determine the loop nest and equal locality then only checks the profit of vectorization.

Macro testcase:

llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1168

This is exactly the else branch of the cache analysis logic. It does not consider the fallback Cost < -LoopInterchangeCostThreshold legacy cost model. Please avoid the code duplication and needing to loop up the CostMap again.

1170–1171

Consider updating this message. Suggestion: "Interchanging loops not considered to improve cache locality nor vectorization."

Meinersbur added inline comments.Nov 16 2022, 9:37 AM
llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1157–1158

The structure suggested in the LoopWG call.

The more general pattern/refactoring would be:

std::optional<bool> shouldInterchange = isProfitableAccordingLoopCacheAnalysis(..);
if (shouldInterchange.has_value()) 
  return shouldInterchange.get_value();

shouldInterchange = isProfitableAccordingInstrOrderCost(..);
if (shouldInterchange.has_value()) 
  return shouldInterchange.get_value();

shouldInterchange = isProfitableForVectorization(..);
if (shouldInterchange.has_value()) 
  return shouldInterchange.get_value();

emitOptimizationRemark("Don't know");

However, this changes when the emitOptimizationRemark is called. If we would not want to change this, it would be (which corresponds to the current structure but with refactoring):

std::optional<bool> shouldInterchange = isProfitableAccordingLoopCacheAnalysis(..);
if (!shouldInterchange.has_value()) {
  shouldInterchange = isProfitableAccordingInstrOrderCost(..);
  if (!shouldInterchange.has_value()) {
     shouldInterchange = isProfitableForVectorization(..);
  }
}

if (!shouldInterchange.has_value())
  emitOptimizationRemark("Don't know");
else if (!shouldInterchange.get_value())
  emitOptimizationRemark("Profitability heuristic indicates this loop is good as-is");

although I would prefer the former over the nested if-else chain and instead emit different optimization remarks for each of the heuristic that indicates that/why the loops should (NOT) be interchanged.

ram-NK marked an inline comment as done.Nov 22 2022, 12:37 PM

@Meinersbur, I will update these comments and testcase accordingly.

ram-NK updated this revision to Diff 479293.Dec 1 2022, 7:48 AM

As per the Michael 's comments. Code inside isProfitable function is cleaned.

Added test case. With two loop interchange passes make sure that, there is no endless interchange. Before this patch there is endless loop interchange possibility. There is no endless interchange after this patch.

ram-NK marked 3 inline comments as done.Dec 1 2022, 7:51 AM

@Meinersbur comments are addressed.

I would suggest to revise the summary and/or the title to describe that this patch now also addresses the endless interchange problem, in addition to correction in isProfitableForVectorization().

llvm/test/Transforms/LoopInterchange/profitability.ll
238 ↗(On Diff #479293)

I would suggest to apply --check-prefix=PROFIT only to interchange_05() and not to other existing tests. Because this opt command line runs interchange twice and shows that there would have been endless interchange with interchange_05() before this patch but there is no endless interchange after this patch, which applies only to interchange_05() and is not related to other tests.

congzhe added inline comments.Dec 1 2022, 8:13 AM
llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1171

Is it possible to make LoopInterchangeCostMeaningfulnessThreshold an opt flag so we can assign its value more flexibly? Possibly rename it to LegacyCostModelThreshold or whatever is more appropriate.

static cl::opt<int> LegacyCostModelThreshold(
    "legacy-cost-threshold", cl::init(10), cl::Hidden,
    cl::desc("The threshold for the legacy cost model to be considered."));
ram-NK updated this revision to Diff 479329.Dec 1 2022, 9:13 AM
ram-NK retitled this revision from [LoopInterchange] Correcting the profitability checking for vectorization to [LoopInterchange] Correcting the profitability check.
ram-NK edited the summary of this revision. (Show Details)
ram-NK marked 2 inline comments as done.

@congzhe Corrected as per the comments.

ram-NK updated this revision to Diff 479630.Dec 2 2022, 7:17 AM

Added comments in Test case 05 for better understanding.
Corrected the comment in Test case 01. The loop interchange decision is made from the Cache cost analysis and is not from vectorization.

ram-NK updated this revision to Diff 479744.Dec 2 2022, 1:55 PM
ram-NK updated this revision to Diff 482045.Dec 12 2022, 2:43 AM
congzhe added inline comments.Dec 12 2022, 3:28 PM
llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1138

nit: isProfitableAccordingToLoopCacheAnalysis, or isProfitablePerLoopCacheAnalysis

1164

nit: isProfitableAccordingToInstrOrderCost, or isProfitablePerInstrOrderCost

1202

It could be worth adding some comments for this function that describe what it does now, and how it prevents endless interchange from happening.

llvm/test/Transforms/LoopInterchange/profitability.ll
175 ↗(On Diff #482045)

nit:

may leads->may lead
before-> before D135808
now-> after D135808

ram-NK updated this revision to Diff 482681.Dec 13 2022, 5:58 PM
ram-NK marked 4 inline comments as done.

@congzhe, All comments are addressed.

congzhe added a reviewer: Restricted Project.Dec 13 2022, 8:13 PM
Matt added a subscriber: Matt.Dec 14 2022, 8:32 AM
bmahjour added inline comments.Dec 15 2022, 12:29 PM
llvm/lib/Transforms/Scalar/LoopInterchange.cpp
64

I don't see why we need two options to control the legacy cost model.

1159

We should return nullopt when/if InnerIndex == OuterIndex

1180

since the idea is to call this function only when CC has failed or been indecisive, we don't need to pass CC to this function and check it here.

llvm/test/Transforms/LoopInterchange/profitability.ll
171–176 ↗(On Diff #482681)

reword:

This tests to make sure, that multiple invocations of loop interchange will not undo previous interchange and will converge to a particular order determined by the profitability analysis.

@bmahjour I will update the comments accordingly.

ram-NK added inline comments.Dec 21 2022, 11:30 AM
llvm/lib/Transforms/Scalar/LoopInterchange.cpp
64

This new parameter added as per @Meinersbur comments in Inline. This is considered as the upper limit of InstrOrderCost.

1159

The CostMap is assigned with unique number to each loop. So this condition "InnerIndex == OuterIndex" will not satisfy. But LoopCost may be same, CC->getLoopCost(*InnerLoop) == CC->getLoopCost(*OuterLoop) checking can be added for returning nullopt.

ram-NK updated this revision to Diff 485617.Dec 29 2022, 8:07 AM
ram-NK marked 4 inline comments as done.EditedDec 29 2022, 8:16 AM

perserve-lcssa.ll corrected, Instruction order cost is 0, Not profitable to interchange.
loop-interchange-optimization-remarks.ll remark message corrected.
All comments are updated.

ram-NK updated this revision to Diff 485706.Dec 30 2022, 9:00 AM

corrected the code formating

ram-NK added a comment.Jan 6 2023, 8:27 AM

Hi @bmahjour, Changes are made as per your comments. Could you get the time to review it.
Hi @Meinersbur, Some changes are done over your suggested comments (https://reviews.llvm.org/D135808?id=474010#inline-1332682)

bmahjour accepted this revision.Jan 10 2023, 2:34 PM

LGTM with some minor comments.

llvm/lib/Transforms/Scalar/LoopInterchange.cpp
1159

ah, ok...in that case please add assert(InnerIndex != OuterIndex && "CostMap should assign unique numbers to each loop")

1185

ideally this should be

if (Row[OuterLoopId] != '=' && Row[OuterLoopId] != 'I')

This revision is now accepted and ready to land.Jan 10 2023, 2:34 PM
Meinersbur accepted this revision.Jan 11 2023, 7:56 AM

LGTM.

Sorry I didn't find time to review it.

llvm/test/Transforms/LoopInterchange/perserve-lcssa.ll
172 ↗(On Diff #485706)

The test fails for me with the latest version because of opaque pointers emitted by opt.

Regeneration of the test should fix this.

ram-NK updated this revision to Diff 488854.Jan 12 2023, 8:14 PM
ram-NK marked 3 inline comments as done.

All comments are updated.

This revision was landed with ongoing or failed builds.Jan 16 2023, 11:36 AM
This revision was automatically updated to reflect the committed changes.