[LLVMdev] MergeFunctions: reduce complexity to O(log(N))

Stepan Dyatkovskiy stpworld at narod.ru
Fri Mar 7 09:47:10 PST 2014


Ping

> ping
> Stepan Dyatkovskiy wrote:
> 
>> Hi Nick,
>>
>> I tried to rework changes as you requested. One of patches (0004 with
>> extra assertions) has been removed.
>>
>>> + bool isEquivalentType(Type *Ty1, Type *Ty2) const {
>>> + return cmpType(Ty1, Ty2) == 0;
>>> + }
>>>
>>> Why do we still need isEquivalentType? Can we nuke this?
>>
>> Yup. After applying all the patches isEquivalentType will be totally
>> replaced with cmpType. All isEqXXXX and friends will be removed in 0011
>> (old 0012). No traces left.
>> Old function wasn't removed in 0001 just for keeping patches without
>> extra noise like:
>>
>> - something that uses isEquivalentType
>> + something that uses cmpType
>>
>> The point is, that "something" that uses isEquivalentType, will be also
>> replaced with one of next patches in this series.
>>
>>> +static int cmpNumbers(uint64_t L, uint64_t R) {
>>> + if (L < R) return -1;
>>> + if (L > R) return 1;
>>> + return 0;
>>> +}
>>>
>>> At a high level, you don't actually need a <=> operator to use a sort. A
>>> strict ordering ( < operator) is sufficient. (Note that for mergefunc, a
>>> strict weak ordering is not sufficient, it must be a total ordering.)
>>
>> That could be done with int FunctionComparator::compare(). We can
>> replace it with bool FunctionComparator::less(). Though for all other
>> cmp methods need at least 2 bits of information as result:
>> 1. Whether things are equal.
>> 2. Whether left less than right.
>>
>> As for FunctionComparator::compare(), conversion into less() will
>> increase time of sanity check (patch #0010).
>> Sanity check is just a sweet bonus. It checks that ordering implemented
>> properly (checks order relation properties).
>> Turning compare() into less() mean, that we'll have to run comparison
>> two times: L.less(R) and R.less(L). But may be sanity check is not a
>> thing to be published at all.
>>
>>> Consider hoisting this inside the FunctionComparator class? That class
>>> should have a bunch of implementations of comparisons between various
>>> different things, which can pass down to other methods in the same
>>
>> class.
>> In new patch series attached to this post, I have moved all static
>> methods into FunctionComparator.
>>
>>> + // Replacement for type::canLosslesslyBitCastTo, that
>>> + // establish order relation on this kind of properties.
>>> + int checkForLosslessBitcast(const Type *L, const Type *R);
>>>
>>> Type:: not type:: . Please make this comment more descriptive.
>>
>> Done.
>> [new comment]
>> Replacement for Type::canLosslesslyBitCastTo, that
>> establish order relation on this kind of properties
>> Returns 0, if L and R types could be converted to each other without
>> reinterpretation of bits.
>> Otherwise method returns -1 or 1, defining total ordering between
>> types in context of lossless bitcastability trait.
>> E.g.: if L is less than R (result is -1), than every type that could be
>> losslessly bitcasted to L is less than R.
>> [/new comment]
>>
>>> + /// Replacement for C1 == getBitCast(C2, C1Ty)
>>> + /// Its more controllable, and supposed to be simpler and more
>>> predictionable.
>>> + /// As very important advantage: it allows to introduce order
>>
>> relation on
>>
>>> + /// constants set, and thus use it as trait in refinement routines.
>>>
>>> "Its" --> "It's". "predictionable" --> "predictable". And how is it more
>>> predictable? I think this comment would be better if it described the
>>> function instead of making comparisons between it and other functions.
>>> Something like, "Compare constants under a system where pointer to X and
>>> pointer to Y are considered equal" or whatever is actually true here.
>>
>> Done.
>> [new comment]
>> Replacement for C1 == getBitCast(C2, C1Ty)
>> Parses constants contents, assuming that types are losslessly
>> bitcasted between each other. So actually it ignores types and only
>> compares bits from L and R.
>> Returns 0, if L and R has equivalent content.
>> -1 or 1 if values are different. Maintaining total ordering requires
>> two values that indicates non-equivalence (L less R, L greater R).
>> [/new comment]
>>
>>> +enum ConstantType {
>>> I'm not sure that this buys you much. All the "isa" tests can be broken
>>> down into a switch on getValueID() with the one exception of
>>
>> isNullValue().
>> Done.
>>
>>> + assert(
>>> + C1->getType()->canLosslesslyBitCastTo(C2->getType()) &&
>>> + "Pass is healthless. checkForLosslessBitcast should be twin of "
>>> + "canLosslesslyBitCastTo method, except the case when the last
>>
>> one "
>>
>>> + "returns false, the first one should return -1 or 1");
>>
>> ...
>>
>>> I think we can skip the asserts here. They aren't detecting a specific
>>> bug, they're checking whether the new code does a certain task relative
>>> to the old code. Drop the old code, your new code is the new sheriff in
>>> town.
>>
>> Asserts has been removed.
>>
>>> + DenseMap<const Value*, int> sn_map1, sn_map2;
>>>
>>> What is sn short for ("seen")? Why are there two of these?
>>
>> Serial number :-)
>>
>>> + std::pair<DenseMap<const Value *, int>::iterator, bool>
>>> + LeftSN = sn_map1.insert(std::make_pair(V1, sn_map1.size())),
>>> + RightSN = sn_map2.insert(std::make_pair(V2, sn_map2.size()));
>>>
>>> So I think I get it, this is easy to reason about. You number each value
>>> going down both functions. But I think you can eliminate one of these
>>> maps because you know that if the left and right were ever different
>>> then we terminate the comparison immediately. You can at least share one
>>> map with both V1 and V2, but I think you can reduce the number of map
>>> insertions.
>>
>> Not sure. Consider that in left you have:
>> %A = alloca i32
>> %B = alloca i32
>> store i32 1, i32* %A
>>
>> And in right:
>> %A = alloca i32
>> %B = alloca i32
>> store i32 1, i32* %B
>>
>> When we meet 'store' instruction we need to check in which order %A was
>> allocated at left and in which order %B was allocated at right.
>> So for each value in function we have to assign serial number. Then
>> comparing to local values from different functions we can just check
>> their serial numbers.
>> Though, may be I missed something? May be there is another way to
>> determine "who was first".
>>
>>> High-level question, are attributes ordered? Your comparison is ordered.
>>> So if I have function F with string attributes "sse2", "gc=hemisphere"
>>> and another function G with string attributes "gc=hemisphere", "sse2"
>>> then they would be considered different. I don't think that's right.
>>
>> I didn't check it. But if it is not ordered, we could order it
>> lexicographically.
>>
>>> + int cmpOperation(const Instruction *I1, const Instruction *I2) const;
>>> +
>>> bool isEquivalentOperation(const Instruction *I1,
>>> - const Instruction *I2) const;
>>> + const Instruction *I2) const {
>>> + return cmpOperation(I1, I2) == 0;
>>> + }
>>>
>>> Hopefully this patch series ultimately eliminates calls of
>>> isEquivalentOperation?
>>
>> Of course. Just like isEquivalentType.
>>
>>> By the way, this is an interesting one. Should "add x, y" and "add nsw
>>> x, y" be equal or not?
>>
>> Yeah. Those are interesting questions. We could even treat 'mul a, 2'
>> and 'shl a,1' as equal in some cases. I think it's matter of time and
>> further optimization.
>>
>> Please find first reworked patches in attachment.
>>
>> -Stepan
>>
>> Nick Lewycky wrote:
>>
>>> /// Compare two Types, treating all pointer types as equal.
>>> - bool isEquivalentType(Type *Ty1, Type *Ty2) const;
>>> + int cmpType(Type *Ty1, Type *Ty2) const;
>>> +
>>> + /// Compare two Types, treating all pointer types as equal.
>>> + bool isEquivalentType(Type *Ty1, Type *Ty2) const {
>>> + return cmpType(Ty1, Ty2) == 0;
>>> + }
>>>
>>> Why do we still need isEquivalentType? Can we nuke this?
>>>
>>> +static int cmpNumbers(uint64_t L, uint64_t R) {
>>> + if (L < R) return -1;
>>> + if (L > R) return 1;
>>> + return 0;
>>> +}
>>>
>>> At a high level, you don't actually need a <=> operator to use a sort. A
>>> strict ordering ( < operator) is sufficient. (Note that for mergefunc, a
>>> strict weak ordering is not sufficient, it must be a total ordering.)
>>>
>>> Consider hoisting this inside the FunctionComparator class? That class
>>> should have a bunch of implementations of comparisons between various
>>> different things, which can pass down to other methods in the same class.
>>>
>>> + // Replacement for type::canLosslesslyBitCastTo, that
>>> + // establish order relation on this kind of properties.
>>> + int checkForLosslessBitcast(const Type *L, const Type *R);
>>>
>>> Type:: not type:: . Please make this comment more descriptive.
>>>
>>> + /// Replacement for C1 == getBitCast(C2, C1Ty)
>>> + /// Its more controllable, and supposed to be simpler and more
>>> predictionable.
>>> + /// As very important advantage: it allows to introduce order
>>> relation on
>>> + /// constants set, and thus use it as trait in refinement routines.
>>>
>>> "Its" --> "It's". "predictionable" --> "predictable". And how is it more
>>> predictable? I think this comment would be better if it described the
>>> function instead of making comparisons between it and other functions.
>>> Something like, "Compare constants under a system where pointer to X and
>>> pointer to Y are considered equal" or whatever is actually true here.
>>>
>>> +enum ConstantType {
>>>
>>> I'm not sure that this buys you much. All the "isa" tests can be broken
>>> down into a switch on getValueID() with the one exception of
>>> isNullValue().
>>>
>>> + // Check whether C2 and C1 has equal bit patterns.
>>> + if (checkForLosslessBitcast(C1->getType(), C2->getType()) != 0)
>>> + return false;
>>> +
>>> + assert(
>>> + C1->getType()->canLosslesslyBitCastTo(C2->getType()) &&
>>> + "Pass is healthless. checkForLosslessBitcast should be twin of "
>>> + "canLosslesslyBitCastTo method, except the case when the last
>>> one "
>>> + "returns false, the first one should return -1 or 1");
>>> +
>>> + // Compare constant contents
>>> + if (cmpConstants(C1, C2) != 0)
>>> + return false;
>>> +
>>> + assert(
>>> + C1 ==
>>> + ConstantExpr::getBitCast(const_cast<Constant*>(C2),
>>> C1->getType()) &&
>>> + "Pass is healthless. cmpConstants should detect *subset* of "
>>> + "equivalent constants that could be detected by "
>>> + "getBitCast method. So whereas cmpConstants returns 0 "
>>> + "getBitCast also must return true. And wheres getBitCast "
>>> + "returns false, cmpConstants also must return -1 or 1.");
>>>
>>> I think we can skip the asserts here. They aren't detecting a specific
>>> bug, they're checking whether the new code does a certain task relative
>>> to the old code. Drop the old code, your new code is the new sheriff in
>>> town.
>>>
>>> + DenseMap<const Value*, int> sn_map1, sn_map2;
>>>
>>> What is sn short for ("seen")? Why are there two of these?
>>>
>>> + std::pair<DenseMap<const Value *, int>::iterator, bool>
>>> + LeftSN = sn_map1.insert(std::make_pair(V1, sn_map1.size())),
>>> + RightSN = sn_map2.insert(std::make_pair(V2, sn_map2.size()));
>>>
>>> So I think I get it, this is easy to reason about. You number each value
>>> going down both functions. But I think you can eliminate one of these
>>> maps because you know that if the left and right were ever different
>>> then we terminate the comparison immediately. You can at least share one
>>> map with both V1 and V2, but I think you can reduce the number of map
>>> insertions.
>>>
>>> +// Mostly cloned from BitcodeWriter, but simplified a bit.
>>>
>>> Missing indent.
>>>
>>> +static int cmpAttrs(const AttributeSet L, const AttributeSet R) {
>>>
>>> High-level question, are attributes ordered? Your comparison is ordered.
>>> So if I have function F with string attributes "sse2", "gc=hemisphere"
>>> and another function G with string attributes "gc=hemisphere", "sse2"
>>> then they would be considered different. I don't think that's right.
>>>
>>> + int cmpOperation(const Instruction *I1, const Instruction *I2) const;
>>> +
>>> bool isEquivalentOperation(const Instruction *I1,
>>> - const Instruction *I2) const;
>>> + const Instruction *I2) const {
>>> + return cmpOperation(I1, I2) == 0;
>>> + }
>>>
>>> Hopefully this patch series ultimately eliminates calls of
>>> isEquivalentOperation?
>>>
>>> + if (int Res = cmpNumbers(I1->getRawSubclassOptionalData(),
>>> + I2->getRawSubclassOptionalData()))
>>>
>>> By the way, this is an interesting one. Should "add x, y" and "add nsw
>>> x, y" be equal or not? Depends whether the backend uses it, and whether
>>> we're going to do more optimization afterwards (ie., this is the compile
>>> step of an LTO build). Nothing for you to change here, just want you to
>>> be aware that this is a point where we might want to have a flag and
>>> make mergefunc behave differently in different compile strategies.
>>>
>>> (I stopped before getting to 0008.)
>>>
>>> Nick
>>>
>>> On 3 February 2014 11:11, Stepan Dyatkovskiy <stpworld at narod.ru
>>> <mailto:stpworld at narod.ru>> wrote:
>>>
>>> Hi all,
>>>
>>> Previous patch has been split onto series of small changes.
>>> On each stage (after each patch) MergeFunctions pass is compilable
>>> and stable.
>>>
>>> Please find patches in attachment for review.
>>>
>>> To apply all patches at once, use "apply-patches.sh" script as
>>> follows:
>>> 0. Place "apply-patches.sh" in same directory with patches.
>>> 1. cd <llvm-sources-dir>
>>> 2. "bash <path-to-patches-dir>/apply-__patches.sh"
>>>
>>> -Stepan
>>>
>>> Stepan Dyatkovskiy wrote:
>>>
>>> Hi Nick,
>>> About partition refinement.
>>>
>>> It looks like patch proposed here allows to apply your idea with
>>> painless way (as series of small changes actually).
>>>
>>> If I got it right, you would like to introduce the next
>>> partition-refinement.
>>>
>>> Initial partition P consists of only one functions class X0
>>> (whole
>>> module, single bucket). Suppose S is subset of X0, so S
>>> represents
>>> functions with some trait that is absent in rest part of X0.
>>>
>>> Then refinement looks like below:
>>>
>>> P`=refine(P, S):
>>> X`[0] = intersection(X[0], S)
>>> X`[1] = difference(X[0], S) // X0 without S0
>>>
>>> so P`={X`[0], X`[1]}
>>>
>>> And actually X`[0] and X`[1] are classes of functions that
>>> differs with
>>> trait defined by S.
>>>
>>> Then (suppose, we need to introduce new trait, so it would be
>>> S`):
>>>
>>> P``=refine(P`, S`), so each X`` could be defined like below:
>>> for each X`[i] in P`:
>>> X``[i*2] = intersection(X`[i], S`)
>>> X``[i*2+1] = difference(X`[i], S`)
>>>
>>> and so on, until all the traits are over.
>>>
>>> Finally we got the classes (set of Xs) that consists of
>>> equivalent
>>> functions. So, we can merge functions inside the class bounds.
>>>
>>> Am I right?
>>>
>>> Patch, that was proposed here could be represented as such
>>> partition
>>> refinement then:
>>>
>>> On each refinement stage, you need to introduce S somehow, the
>>> algorithm
>>> that answers the question: "what sort of difference we want to
>>> check in
>>> this bucket?"
>>>
>>> In existing implementation we check traits already, but pair-wise
>>> (attributes, calling convention, number of arguments, number of
>>> basic
>>> blocks, etc.). How to to use the same traits in partition
>>> refinement?
>>>
>>> Consider next example. Suppose we want to split some bucket X:
>>>
>>> 0. Take some trait from *pair-wise* comparison. For example:
>>> number of
>>> arguments.
>>> 1. Take some function from bucket X, let it be F1. Let F1 has N1
>>> number
>>> of arguments.
>>> 2. Then S are functions with number of arguments *less* than N1.
>>> 3. Do refinement by S. As result we got X`[0] and X`[1].
>>> 4. If both of X`[0] and X`[1] are not empty, then repeat #1-#3
>>> for them.
>>> 5. If some of X`[0] of X`[1] is empty set, then all functions in
>>> X are
>>> the same in context of trait S. We need another trait, go to
>>> #0. And
>>> repeat #0-#5 for all X` buckets we have.
>>>
>>> Finally we got the groups of equivalent functions. And actually
>>> these
>>> groups would be organized in binary tree.
>>> Most of stuff is implemented in std::map. I showed it as #0-#5
>>> just to
>>> put it into context of your idea. The only problem was to
>>> introduce way
>>> of how to define order-relation (or how to define S). But this
>>> problem
>>> has been solved.
>>> And on output we got groups of equal functions.
>>>
>>> Fix me if I'm wrong. But it looks like every advantage given by
>>> partition refinement is present here.
>>>
>>> As I said in beginning, it is way of how to commit things you
>>> proposed
>>> with minimum of changes. We will use old comparison routines,
>>> only thing
>>> we need to change is return type (from bool to -1,0,1).
>>> In this way, partition refinement could be committed as series
>>> of small
>>> changes: one comparison routine per commit.
>>>
>>> Every change in MergeFunctions from now could be well checked by
>>> this
>>> builder:
>>> Now we have special builder for MergeFunctions. It runs
>>> test-suite with
>>> forced MergeFunctions pass:
>>>
>>> http://lab.llvm.org:8011/__builders/clang-mergefunc-x86___64-freeBSD9.2
>>>
>>> <http://lab.llvm.org:8011/builders/clang-mergefunc-x86_64-freeBSD9.2>
>>> Currently, MergeFunctions pass is stable, builder is green and
>>> passes
>>> all test-suite programs.
>>>
>>> -Stepan.
>>>
>>> Stepan Dyatkovskiy wrote:
>>>
>>> Hi all,
>>> Please find the updated patch in attachment:
>>> * Added some comments.
>>> * Fixed some typos.
>>>
>>> -Stepan
>>>
>>> Nick Lewycky wrote:
>>>
>>> On 30 January 2014 01:27, Stepan Dyatkovskiy
>>> <stpworld at narod.ru <mailto:stpworld at narod.ru>
>>> <mailto:stpworld at narod.ru <mailto:stpworld at narod.ru>>>
>>> wrote:
>>>
>>> Hello Sean and Tobias,
>>>
>>> Sean,
>>> Thank you. Could you describe Nick's ideas in few
>>> words or give me
>>> links to your discussion, so I could adapt my ideas
>>> to it.
>>>
>>> Sorry I haven't had time to write it up. The essential
>>> idea is that we
>>> use partition-refinement to determine the differences
>>> between a group of
>>> functions instead of using pair-wise comparison.
>>>
>>> Imagine you want to compare long sequences of numbers.
>>> You can grab a
>>> pair of sequences and run down them until they differ or
>>> you reach the
>>> end, and move on to another pair of sequences. With
>>> partition refinement
>>> you start with the first number in all the sequences and
>>> bucket them.
>>> Say, three 1's, two 2's, and an 8. You've just formed 3
>>> buckets. For
>>> each bucket (with more than 1 member), do it again with
>>> the next number
>>> in the sequence. This gets you an equivalence comparison
>>> at less than
>>> the complexity of pairwise comparisons. (The buckets are
>>> partitions, and
>>> you're refining them as you read more data. Partition
>>> refinement is the
>>> dual of union-find, which we use quite heavily in the
>>> compiler.)
>>>
>>> I haven't figured out how this stacks up against
>>> Stepan's sorting with
>>> <=> comparison. I can probably do that, I just haven't
>>> spent the time
>>> thinking about it.
>>>
>>> I also haven't thought through how to integrate it with
>>> "near miss"
>>> comparison. My current leading idea is that you do the
>>> same thing, but
>>> make a record of what makes two buckets similar. (Note
>>> that the wrong
>>> solution is to store a per-function-pair list of
>>> similarities/differences.)
>>>
>>> Nick
>>>
>>> Tobias,
>>> Your patch fails on several modules in my benchmark
>>> (73 of ~1800
>>> tests). I have sent one as attachment.
>>>
>>> See statistics files for more details, all the .ll
>>> files you could
>>> simply find in test-suite object directory (after
>>> "make TEST=nightly
>>> report").
>>> I have attached script that launches benchmark.
>>> Example of usage
>>> (you need Linux OS):
>>> bash test-scipt.sh report.txt opt-no-patch
>>> opt-with-patch
>>>
>>> Some numbers.
>>>
>>> Total number of functions: 52715
>>>
>>> Below, by default, I have excluded modules where
>>> your patch fails.
>>>
>>> Functions merged
>>> Original version: 1113
>>> Order relation patch: 1112
>>> Similar merging patch: 541
>>>
>>> Functions merged (including failed tests)
>>> Original version: 8398
>>> Order relation patch: 8395
>>>
>>> Summary files size
>>> Initial: 163595634
>>> bytes.
>>> After merging with order relation patch: 163147731
>>> bytes.
>>> After similar merging patch: 162491905
>>> bytes.
>>>
>>> Summary files size (including failed tests)
>>> Initial: 250242266 bytes.
>>> Original version: 247168198 bytes.
>>> Order relation: 247175650 bytes.
>>>
>>> Time. I measured with "time" utility, and used
>>> "real (wall clock)
>>> time used by the process".
>>>
>>> Summary time spent
>>> With existing version: 28.05 secs.
>>> With order relation patch: 28.19 secs.
>>> Similar merging patch: 28.61 secs.
>>>
>>> Summary time spent (including failed tests)
>>> With existing version: 41.74 secs.
>>> With order relation patch: 36.35 secs.
>>>
>>> -Stepan
>>>
>>> Sean Silva wrote:
>>>
>>> On Tue, Jan 28, 2014 at 2:47 PM, Tobias von Koch
>>> <tobias.von.koch at gmail.com
>>> <mailto:tobias.von.koch at gmail.com>
>>> <mailto:tobias.von.koch at gmail.__com
>>> <mailto:tobias.von.koch at gmail.com>>
>>> <mailto:tobias.von.koch at gmail.
>>> <mailto:tobias.von.koch at gmail.>____com
>>> <mailto:tobias.von.koch at gmail.__com
>>> <mailto:tobias.von.koch at gmail.com>>>> wrote:
>>>
>>> Hi Stepan,
>>>
>>> Sorry for the delay. It's great that you
>>> are working on
>>> MergeFunctions as well and I agree, we
>>> should definitely
>>> try to
>>> combine our efforts to improve
>>> MergeFunctions.
>>>
>>> Just to give you some context, the pass
>>> (with the similar
>>> function
>>> merging patch) is already being used in a
>>> production
>>> setting. From
>>> my point of view, it would be better if we
>>> focus on
>>> improving its
>>> capability of merging functions at this
>>> stage rather
>>> than on
>>> reduction of compilation time.
>>>
>>> I'll give a brief overview of how our
>>> similar function
>>> merging
>>> algorithm works (you can also watch the
>>> presentation from
>>> the US
>>> LLVM conference to hear me explain it
>>> using an animation).
>>>
>>> 1. Hash all functions into buckets
>>>
>>> In each bucket, separately:
>>>
>>> 2. Compare functions pair-wise and
>>> determine a
>>> similarity metric for each pair (%age
>>> of equivalent
>>> instructions)
>>>
>>> 3. Merge identical functions (with
>>> similarity = 100%),
>>> update call
>>> sites for those functions.
>>>
>>> 4. If the updates of call sites have
>>> touched other
>>> functions,
>>> go back to step 2 and re-compare
>>> *only* those
>>> functions to all
>>> others in their buckets.
>>>
>>> Finally,
>>>
>>> 5. Form groups of similar functions for
>>> merging:
>>> a) Pick most similar pair (A,B)
>>> b) Find all functions C that are also
>>> similar to B but
>>> are not
>>> more similar to some other
>>> function D.
>>> c) Merge A with B and all the C's.
>>> Repeat until there are no more
>>> functions to merge.
>>>
>>> As you can see, we need to compute a
>>> similarity measure for
>>> each
>>> pair of functions in a bucket. If I
>>> understand correctly,
>>> your patch
>>> reduces compile-time by avoiding
>>> comparisons of functions
>>> that
>>> cannot be identical. That's exactly what
>>> you want to do if
>>> you only
>>> want to merge identical functions.
>>> However, because we also
>>> need to
>>> determine whether functions are merely
>>> *similar*, I can't
>>> see how
>>> your idea could be applied in that case.
>>>
>>> Yeah, the existing pass only tries to merge
>>> identical functions
>>> (identical in machine code). I'm wondering if
>>> the "similar"
>>> function
>>> merging would be better as a separate pass (I'm
>>> genuinely
>>> wondering; I
>>> have no clue). I would wait for Nick's feedback.
>>>
>>> -- Sean Silva
>>>
>>> Looking at your statistics, I see that you
>>> are considering
>>> a very
>>> large number of functions. I don't know
>>> which benchmarks
>>> you are
>>> using, but I doubt that many of these are
>>> worth merging.
>>> For
>>> instance, you rarely gain a code size
>>> benefit from merging
>>> functions
>>> with just a few instructions. Our patch
>>> takes care of this
>>> using
>>> thresholds. It's worth looking at actual
>>> code size
>>> reductions,
>>> rather than just numbers of functions
>>> merged/ compilation
>>> time spent
>>> comparing functions. It turns out that the
>>> number of
>>> functions in
>>> each bucket is really quite small once you
>>> apply some
>>> heuristics
>>> (and could still be further reduced!).
>>>
>>> Given your experience with MergeFunctions,
>>> it would be
>>> really great
>>> if you could review our patch and also try
>>> it out on your
>>> benchmarks.
>>>
>>> Tobias
>>>
>>> On 24/01/2014 19:11, Stepan Dyatkovskiy
>>> wrote:
>>>
>>> Hi Tobias.
>>>
>>> So, what do you think?
>>>
>>> If it means to much extra-job for your
>>> team, may be I
>>> can help you
>>> somehow? I really would like to.
>>>
>>> -Stepan
>>>
>>> Stepan Dyatkovskiy wrote:
>>>
>>> Hi Tobias,
>>>
>>> I can't really see a way to
>>> combine our
>>> approach with
>>> your patch. What
>>> are your thoughts?
>>>
>>> I think it is possible. Even more
>>> - we need to
>>> combine our
>>> efforts, in
>>> order to bring this pass into real
>>> live.
>>> I'have read your presentation
>>> file, and
>>> unfortunately read
>>> your patch
>>> only a little.
>>> How exactly you scan functions on
>>> 2nd stage?
>>> Could you
>>> explain the
>>> algorithm in few words, how you
>>> compare workflow?
>>> Is it
>>> possible to
>>> scan binary tree instead of hash
>>> table? ...OK.
>>>
>>> That's how I see the modification.
>>> Now its
>>> important to
>>> explain idea,
>>> so consider the simplest case: you
>>> need to catch
>>> two
>>> functions that
>>> are differs with single
>>> instruction somewhere.
>>>
>>> 1. Imagine, that IR *module*
>>> contents is
>>> represented as
>>> binary tree:
>>> Each line (trace) from root to
>>> terminal node is a
>>> function.
>>> Each node - is function's
>>> primitive (instruction
>>> opcode, for
>>> example).
>>> Down - is direction to terminal
>>> nodes, up - is
>>> direction to
>>> the root.
>>>
>>> 2. Now you are standing on some
>>> node. And you
>>> have two
>>> directions
>>> down-left and down-right.
>>>
>>> 3. If you are able to find two
>>> equal sub-traces
>>> down (at
>>> left and at
>>> right), then the only difference
>>> lies in this node.
>>> Then we
>>> catch that
>>> case.
>>>
>>> 4. Even more, if two subtrees at
>>> left and at
>>> right are
>>> equal, than you
>>> catch at once all the cases that
>>> are represented by
>>> these
>>> subtrees.
>>>
>>> I still didn't look at you patch
>>> carefully. Sorry..
>>> But I
>>> hope that
>>> helps, and I'll try look at it in
>>> nearest time and
>>> perhaps
>>> its not the
>>> best solution I gave in this post.
>>>
>>> -Stepan
>>>
>>> 22.01.2014, 20:53, "Tobias von
>>> Koch"
>>> <tobias.von.koch at gmail.com
>>> <mailto:tobias.von.koch at gmail.com>
>>> <mailto:tobias.von.koch at gmail.__com
>>> <mailto:tobias.von.koch at gmail.com>>
>>> <mailto:tobias.von.koch at gmail.
>>> <mailto:tobias.von.koch at gmail.>____com
>>> <mailto:tobias.von.koch at gmail.__com
>>> <mailto:tobias.von.koch at gmail.com>>>>:
>>>
>>> Hi Stepan,
>>>
>>> As you've seen we have
>>> recently implemented a
>>> significant enhancement to
>>> the MergeFunctions pass that
>>> also allows
>>> merging of
>>> functions that are
>>> only similar but not identical
>>>
>>> (http://llvm-reviews.__chandle____rc.com/D2591
>>> <http://chandle__rc.com/D2591>
>>> <http://chandlerc.com/D2591>
>>>
>>> <http://llvm-reviews.__chandle__rc.com/D2591
>>> <http://chandlerc.com/D2591>
>>> <http://llvm-reviews.__chandlerc.com/D2591
>>> <http://llvm-reviews.chandlerc.com/D2591>>>).
>>>
>>> Our patch also changes the way
>>> that functions
>>> are
>>> compared quite
>>> significantly. This is
>>> necessary because we
>>> need to
>>> compare all
>>> functions in a bucket in order
>>> to get a
>>> similarity
>>> measure for each
>>> pair, so we can then decide
>>> which 'groups' of
>>> functions
>>> to merge.
>>>
>>> I can't really see a way to
>>> combine our
>>> approach with
>>> your patch. What
>>> are your thoughts?
>>>
>>> Another way to improve the
>>> performance of
>>> MergeFunctions
>>> might be to
>>> make the hash function better.
>>> I've already
>>> added the
>>> size of the first
>>> BB to it, and perhaps there
>>> are other factors
>>> we could
>>> try... if we
>>> don't have to compare
>>> functions in the first
>>> place
>>> (because they're in
>>> different buckets) then that's
>>> obviously a much
>>> bigger win.
>>>
>>> Thanks,
>>> Tobias
>>>
>>> On 17/01/2014 20:25, Stepan
>>> Dyatkovskiy wrote:
>>>
>>> Hi all,
>>>
>>> I propose simple
>>> improvement for
>>> MergeFunctions
>>> pass, that reduced
>>> its
>>> complexity from O(N^2)
>>> to O(log(N)),
>>> where N is
>>> number of
>>> functions in
>>> module.
>>>
>>> The idea, is to replace
>>> the result of
>>> comparison
>>> from "bool" to
>>> "-1,0,1". In another
>>> words: define order
>>> relation
>>> on functions set.
>>> To be sure, that
>>> functions could be
>>> comparable
>>> that way, we have to
>>> prove that order
>>> relation is possible
>>> on all
>>> comparison stage.
>>>
>>> The last one is
>>> possible, if for each
>>> comparison
>>> stage we
>>> implement has
>>> next properties:
>>> * reflexivity (a <= a,
>>> a == a, a >= a),
>>> * antisymmetry (if a <=
>>> b and b <= a
>>> then a == b),
>>> * transitivity (a <= b
>>> and b <= c, then
>>> a <= c)
>>> * asymmetry (if a < b,
>>> then a > b or a
>>> == b).
>>>
>>> Once we have defined
>>> order relation we
>>> can store
>>> all the functions in
>>> binary tree and perform
>>> lookup in
>>> O(log(N)) time.
>>>
>>> This post has two
>>> attachments:
>>> 1. The patch, that has
>>> implementation of
>>> this idea.
>>> 2. The MergeFunctions
>>> pass detailed
>>> description,
>>> with explanation how
>>> order relation could be
>>> possible.
>>>
>>> Hope it helps to make
>>> things better!
>>>
>>> -Stepan.
>>>
>>> _____________________________________________________
>>> LLVM Developers mailing
>>> list
>>> LLVMdev at cs.uiuc.edu <mailto:LLVMdev at cs.uiuc.edu>
>>> <mailto:LLVMdev at cs.uiuc.edu <mailto:LLVMdev at cs.uiuc.edu>>
>>> <mailto:LLVMdev at cs.uiuc.edu
>>> <mailto:LLVMdev at cs.uiuc.edu> <mailto:LLVMdev at cs.uiuc.edu
>>> <mailto:LLVMdev at cs.uiuc.edu>>>
>>> http://llvm.cs.uiuc.edu
>>> http://lists.cs.uiuc.edu/______mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/____mailman/listinfo/llvmdev>
>>>
>>> <http://lists.cs.uiuc.edu/____mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/__mailman/listinfo/llvmdev>>
>>>
>>> <http://lists.cs.uiuc.edu/____mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/__mailman/listinfo/llvmdev>
>>>
>>> <http://lists.cs.uiuc.edu/__mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev>>>
>>>
>>> _____________________________________________________
>>> LLVM Developers mailing list
>>> LLVMdev at cs.uiuc.edu <mailto:LLVMdev at cs.uiuc.edu>
>>> <mailto:LLVMdev at cs.uiuc.edu <mailto:LLVMdev at cs.uiuc.edu>>
>>> <mailto:LLVMdev at cs.uiuc.edu
>>> <mailto:LLVMdev at cs.uiuc.edu> <mailto:LLVMdev at cs.uiuc.edu
>>> <mailto:LLVMdev at cs.uiuc.edu>>>
>>> http://llvm.cs.uiuc.edu
>>> http://lists.cs.uiuc.edu/______mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/____mailman/listinfo/llvmdev>
>>>
>>> <http://lists.cs.uiuc.edu/____mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/__mailman/listinfo/llvmdev>>
>>>
>>> <http://lists.cs.uiuc.edu/____mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/__mailman/listinfo/llvmdev>
>>>
>>> <http://lists.cs.uiuc.edu/__mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev>>>
>>>
>>> _________________________________________________
>>> LLVM Developers mailing list
>>> LLVMdev at cs.uiuc.edu <mailto:LLVMdev at cs.uiuc.edu>
>>> <mailto:LLVMdev at cs.uiuc.edu <mailto:LLVMdev at cs.uiuc.edu>>
>>> http://llvm.cs.uiuc.edu
>>> http://lists.cs.uiuc.edu/__mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev>
>>>
>>> _________________________________________________
>>> LLVM Developers mailing list
>>> LLVMdev at cs.uiuc.edu <mailto:LLVMdev at cs.uiuc.edu>
>>> http://llvm.cs.uiuc.edu
>>> http://lists.cs.uiuc.edu/__mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev>
>>>
>>> _________________________________________________
>>> LLVM Developers mailing list
>>> LLVMdev at cs.uiuc.edu <mailto:LLVMdev at cs.uiuc.edu>
>>> http://llvm.cs.uiuc.edu
>>> http://lists.cs.uiuc.edu/__mailman/listinfo/llvmdev
>>> <http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev>
> 
> _______________________________________________
> LLVM Developers mailing list
> LLVMdev at cs.uiuc.edu http://llvm.cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev


 -- 
Truly yours,
Stepan Dyatkovskiy



More information about the llvm-dev mailing list