Minor optimizations of rule indexing/processing

This commit is contained in:
Fedor Isakov 2024-10-03 13:07:36 +02:00
parent 4a97d3e89f
commit 20a6b70de1
5 changed files with 48 additions and 63 deletions

View File

@ -17,11 +17,8 @@
package jetbrains.mps.logic.reactor.core
import gnu.trove.map.hash.TIntObjectHashMap
typealias DispatchingFrontState = Map<Any, RuleMatcher>
internal fun emptyFrontState(): DispatchingFrontState = emptyMap()
import jetbrains.mps.logic.reactor.program.Rule
import java.util.IdentityHashMap
/**
* A front-end interface to [RuleMatcher].
@ -30,38 +27,38 @@ internal fun emptyFrontState(): DispatchingFrontState = emptyMap()
*/
class Dispatcher (val ruleIndex: RuleIndex) {
private val ruletag2matcher = HashMap<Any, RuleMatcher>()
private val matcherByRule = IdentityHashMap<Rule, RuleMatcher>()
/**
* Create new empty [DispatchingFront] ready to accept constraints.
*/
fun front() = DispatchingFront()
fun getOrCreateMatcher(ruleTag: Any): RuleMatcher =
ruletag2matcher.getOrPut(ruleTag) { createRuleMatcher(ruleIndex, ruleTag) }
fun getOrCreateMatcher(rule: Rule): RuleMatcher =
matcherByRule.getOrPut(rule) { createRuleMatcher(rule) }
inner class DispatchingFront {
private val ruletag2probe : HashMap<Any, RuleMatchingProbe>
private val probeByRule : IdentityHashMap<Rule, RuleMatchingProbe>
private val occId2tags : TIntObjectHashMap<MutableList<Any>>
private val occId2tags : IdentityHashMap<Occurrence, MutableList<Rule>>
private val matching: Iterable<RuleMatchingProbe>?
constructor() {
this.ruletag2probe = hashMapOf()
this.occId2tags = TIntObjectHashMap()
this.probeByRule = IdentityHashMap()
this.occId2tags = IdentityHashMap()
this.matching = null
}
private constructor(pred: DispatchingFront, matching: Iterable<RuleMatchingProbe>? = null) {
this.ruletag2probe = pred.ruletag2probe
this.probeByRule = pred.probeByRule
this.occId2tags = pred.occId2tags
this.matching = matching
}
fun getOrCreateProbe(ruleTag: Any): RuleMatchingProbe =
ruletag2probe.getOrPut(ruleTag) { getOrCreateMatcher(ruleTag).probe() }
fun getOrCreateProbe(rule: Rule): RuleMatchingProbe =
probeByRule.getOrPut(rule) { getOrCreateMatcher(rule).probe() }
/**
* Returns all matches satisfied by the constraint occurrences received so far.
@ -72,9 +69,7 @@ class Dispatcher (val ruleIndex: RuleIndex) {
allMatches.addAll(probe.matches())
}
return allMatches
}
fun state() : DispatchingFrontState = ruletag2matcher //.asMap()
}
/**
* Returns a [DispatchingFront] instance that is "expanded" with matches corresponding to the
@ -83,19 +78,16 @@ class Dispatcher (val ruleIndex: RuleIndex) {
fun expand(activated: Occurrence): DispatchingFront = DispatchingFront(this,
ruleIndex.forOccurrenceWithMask(activated)
.map { (rule, mask) ->
if (!occId2tags.contains(activated.identity)) {
occId2tags.put(activated.identity, arrayListOf())
}
occId2tags[activated.identity].add(rule.uniqueTag())
getOrCreateProbe(rule.uniqueTag()).expand(activated, mask) })
occId2tags.computeIfAbsent(activated) { _ -> arrayListOf() }.add(rule)
getOrCreateProbe(rule).expand(activated, mask) })
/**
* Returns a [DispatchingFront] instance that is "contracted": all matches corresponding to the
* specified discarded constraint occurrence are eliminated.
*/
fun contract(discarded: Occurrence): DispatchingFront = DispatchingFront(this,
occId2tags.remove(discarded.identity)
?.mapNotNull { ruleTag -> ruletag2probe[ruleTag] }
occId2tags.remove(discarded)
?.mapNotNull { ruleTag -> probeByRule[ruleTag] }
?.map { probe -> probe.contract(discarded) })
/**
@ -103,7 +95,7 @@ class Dispatcher (val ruleIndex: RuleIndex) {
* be excluded from any further "match" set returned by [matches].
*/
internal fun consume(consumedMatch: RuleMatchEx): DispatchingFront {
ruletag2probe[consumedMatch.rule().uniqueTag()]?.consume(consumedMatch)
probeByRule[consumedMatch.rule()]?.consume(consumedMatch)
return DispatchingFront(this)
}

View File

@ -17,7 +17,6 @@
package jetbrains.mps.logic.reactor.core
import gnu.trove.TIntObjectHashMap
import gnu.trove.TObjectIntHashMap
import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.logical.MetaLogical
@ -29,16 +28,22 @@ import kotlin.collections.ArrayList
import kotlin.collections.HashMap
typealias RuleBits = BitSet
typealias HeadBits = BitSet
fun emptyRuleBits() = BitSet()
fun emptyHeadBits() = BitSet()
/**
* A container for [Rule] instances with the ability to look up by [ConstraintOccurrence].
*
* @author Fedor Isakov
*/
class RuleIndex(rules: Iterable<Rule>, profiler: Profiler? = null) : Iterable<Rule>, RuleLookup
class RuleIndex(rules: Iterable<Rule>, profiler: Profiler? = null) : RuleLookup
{
private class IndexedRule(var idx: Int, val rule: Rule)
private class OrderedRule(var order: Int, val rule: Rule)
// Terminology:
// ruleBit - rule's index in the rules list
@ -49,13 +54,9 @@ class RuleIndex(rules: Iterable<Rule>, profiler: Profiler? = null) : Iterable<Ru
private val tag2rule = HashMap<Any, Rule>()
private val tag2bit = TObjectIntHashMap<Any>()
var nextBit: Int = 0
private val bit2ruleAndMask = TIntObjectHashMap<Pair<IndexedRule, SlotMask>>()
private val allRules = LinkedList<IndexedRule>()
private val bit2ruleAndMask = TIntObjectHashMap<Pair<OrderedRule, SlotMask>>()
init {
profiler.profile("build rule index") {
@ -70,23 +71,23 @@ class RuleIndex(rules: Iterable<Rule>, profiler: Profiler? = null) : Iterable<Ru
*/
fun forOccurrence(occ: ConstraintOccurrence): Iterable<Rule> {
val (ruleBits, slotMasks) = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList()
val result = ArrayList<IndexedRule>()
val result = ArrayList<OrderedRule>()
val itr = ruleBits.iterator()
while (itr.hasNext()) {
bit2ruleAndMask[itr.next()]?.let{
result.add(it.first)
}
}
result.sortBy { it.idx }
result.sortBy { it.order }
return result.map { it.rule }
}
/**
* Returns a pair of rule and bit mask with 1's marking matching slots in constraint's head.
* Returns a pair of rule and bit mask with 1's marking matching slots in rule's head.
*/
fun forOccurrenceWithMask(occ: ConstraintOccurrence): Iterable<Pair<Rule, BitSet>> {
val (ruleBits, argSlotMasks) = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList()
val result = ArrayList<Pair<IndexedRule, BitSet>>()
val result = ArrayList<Pair<OrderedRule, BitSet>>()
val itr = ruleBits.iterator()
while (itr.hasNext()) {
val ruleBit = itr.next()
@ -98,21 +99,17 @@ class RuleIndex(rules: Iterable<Rule>, profiler: Profiler? = null) : Iterable<Ru
}
}
// }
result.sortBy { it.first.idx }
result.sortBy { it.first.order }
return result.map { it.first.rule to it.second }
}
override fun iterator(): Iterator<Rule> = allRules.map { it.rule }.iterator()
fun buildIndexFromRules(rules: Iterable<Rule>) {
rules.forEachIndexed { idx, rule ->
val irule = IndexedRule(idx, rule)
addRuleToIndex(irule)
allRules.add(irule)
addRuleToIndex(OrderedRule(idx, rule))
}
}
private fun addRuleToIndex(irule: IndexedRule) {
private fun addRuleToIndex(irule: OrderedRule) {
val uniqueTag = irule.rule.uniqueTag()
if (tag2rule.containsKey(uniqueTag)) throw IllegalStateException("duplicate rule tag $uniqueTag")
val head = irule.rule.headKept() + irule.rule.headReplaced()
@ -123,7 +120,6 @@ class RuleIndex(rules: Iterable<Rule>, profiler: Profiler? = null) : Iterable<Ru
}
tag2rule[uniqueTag] = irule.rule
tag2bit.put(uniqueTag, nextBit)
bit2ruleAndMask.put(nextBit, irule to slotMask)
nextBit += 1
@ -131,7 +127,7 @@ class RuleIndex(rules: Iterable<Rule>, profiler: Profiler? = null) : Iterable<Ru
/**
* Represents a mask associated with a single rule.
* The mask tells whether or not a particular constraint occurrence can match
* The mask tells whether a particular constraint occurrence can match
* any of the rule's constraints.
*/
private class SlotMask {
@ -208,9 +204,11 @@ class RuleIndex(rules: Iterable<Rule>, profiler: Profiler? = null) : Iterable<Ru
}
/**
* Returns bit set where 1's indicate the indices of matching rules.
* Returns bit set where 1's indicate the indices of matching rules in the first component.
* The second component contains a map of rule bit to a mask with 1's corresponding
* to the occurrence position in the rule's head.
*/
fun select(occ: ConstraintOccurrence): Pair<RuleBits, Map<Int, BitSet>> {
fun select(occ: ConstraintOccurrence): Pair<RuleBits, Map<Int, HeadBits>> {
if (occ.constraint().symbol() != symbol) throw IllegalArgumentException()
// by default select all rules where this constraint is in the head
@ -264,11 +262,11 @@ class RuleIndex(rules: Iterable<Rule>, profiler: Profiler? = null) : Iterable<Ru
selectedRuleBits.retainAll(symbolSelector)
val slotMasks = HashMap<Int, BitSet>()
val slotMasks = HashMap<Int, HeadBits>()
for ((p, votes) in slotVotes.entries) {
val (ruleBit, headPos) = p
if (!votes.isEmpty || !wildcardSlots.isEmpty) {
slotMasks.getOrPut(ruleBit) { BitSet() }.set(headPos)
slotMasks.getOrPut(ruleBit) { emptyHeadBits() }.set(headPos)
}
}

View File

@ -38,13 +38,9 @@ interface RuleMatcher {
fun probe(): RuleMatchingProbe
fun setRuleLookup(ruleLookup: RuleLookup)
fun resetRuleLookup()
}
fun createRuleMatcher(lookup: RuleLookup, tag: Any): RuleMatcher = ReteRuleMatcherImpl(lookup, tag)
fun createRuleMatcher(rule: Rule): RuleMatcher = ReteRuleMatcherImpl(rule)
// Trove stuff
typealias Signature = TIntList

View File

@ -44,21 +44,20 @@ fun Signature.toTrail() = TIntHashSet(this)
fun signatureIndexOf() = TIntObjectHashMap<MutableList<Signature>>()
internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
private val tag: Any) : RuleMatcher
internal class ReteRuleMatcherImpl(private val rule: Rule) : RuleMatcher
{
val head = (lookupRule().headKept() + lookupRule().headReplaced()).toList()
val propagation = lookupRule().headReplaced().count() == 0
fun lookupRule(): Rule = ruleLookup?.lookupRuleByTag(tag) ?: throw IllegalStateException("can't lookup rule by tag: '${tag}'")
fun lookupRule(): Rule = rule
override fun rule() = lookupRule()
override fun setRuleLookup(ruleLookup: RuleLookup) { this.ruleLookup = ruleLookup }
override fun resetRuleLookup() { this.ruleLookup = null }
// override fun setRuleLookup(ruleLookup: RuleLookup) { this.ruleLookup = ruleLookup }
//
// override fun resetRuleLookup() { this.ruleLookup = null }
override fun newProbe(): RuleMatchingProbe = ReteNetwork(head.size).also { probe = it }

View File

@ -28,7 +28,7 @@ class Builder(var rulesList: List<Rule>) : RuleLookup {
override fun lookupRuleByTag(tag: Any): Rule? = tag2rule[tag]
fun ruleMatcher(): RuleMatcher = createRuleMatcher(this, rules.first().uniqueTag())
fun ruleMatcher(): RuleMatcher = createRuleMatcher(rules.first())
fun program(name: String): Program = programBuilder.program(name, rulesList)