Search code examples
memoryclipsconsumption

CLIPS huge memory usage


I'm using the clips framework to build an expert system. However I'm having trouble with memory usage atm, which would make it unsuitable for my task. So here is the problem:

It generates 144 of the SpinWave facts as its supposed to, with a total of around 150 facts. Each fact should not hold more than a bunch of integers (roughly 15). CLIPS consumes 1GB of memory with roughly 6mio memory requests made. I'm kind of baffled why it allocates that much memory... Can someone point me into the right direction or give an explanation. The code I'm using is below. Thanks in advance!

Steve

; define helicity wave final or initial state template
(deftemplate SpinWaveMultiplet
    (slot unique_id (type INTEGER))
    (slot charge (type INTEGER))
    (slot isospin_num (type INTEGER))
    (slot isospin_denom (type INTEGER))
    (slot isospin_z_num (type INTEGER))
    (slot spin_num (type INTEGER))
    (slot spin_denom (type INTEGER))
    ; we have multislot of spin z to allow for specific components
    ; in the initial or final state
    (multislot spin_z_num)
    (slot parity (type INTEGER))
    (slot cparity (type INTEGER))
)

; define spin wave  
(deftemplate SpinWave
    (slot unique_id (type INTEGER))
    (slot charge (type INTEGER))
    (slot isospin_num (type INTEGER))
    (slot isospin_denom (type INTEGER))
    (slot isospin_z_num (type INTEGER))
    (slot spin_num (type INTEGER))
    (slot spin_denom (type INTEGER))
    (slot spin_z_num (type INTEGER))
    (slot parity (type INTEGER))
    (slot cparity (type INTEGER))
)

; allowed intermediate state spins
(deffacts user-conditions
    (AllowedQN
        (spin_nums 0 1 2) (spin_denom 1 ) (isospin_nums 0 1) (isospin_denom 1)
        (charge 0) (parity -1 1) (cparity -1 1)
    )
)


(deffacts initial-state
    (SpinWaveMultiplet (unique_id 0) (spin_num 1) (spin_denom 1) (spin_z_num -1 1)
        (isospin_num 0) (isospin_denom 1) (isospin_z_num 0)
    )
)

(deffacts final-state-list
    (SpinWaveMultiplet (unique_id 1) (spin_num 1) (spin_denom 1) (spin_z_num -1 1)
        (isospin_num 0) (isospin_denom 1) (isospin_z_num 0)
    )
    (SpinWaveMultiplet (unique_id 2) (spin_num 0) (spin_denom 1) (spin_z_num 0)
        (isospin_num 1) (isospin_denom 1) (isospin_z_num 0)
)
    (SpinWaveMultiplet (unique_id 3) (spin_num 0) (spin_denom 1) (spin_z_num 0)
        (isospin_num 1) (isospin_denom 1) (isospin_z_num 0)
    )
)

; create all spin waves
(defrule create-initial-spin-waves
    (AllowedQN
        (spin_nums $?spin_nums) (spin_denom ?spin_denom) 
        (isospin_nums $?isospin_nums) (isospin_denom ?isospin_denom)
        (charge $?charges)
        (parity $?parities)
        (cparity $?cparities)
    )
    =>
    (foreach ?charge ?charges
    (foreach ?parity ?parities
    (foreach ?cparity ?cparities

    (foreach ?isospin_num ?isospin_nums
        (bind ?isospin_z_num (* -1 ?isospin_num))
        (while (<= ?isospin_z_num ?isospin_num)
            (foreach ?spin_num ?spin_nums
                (bind ?spin_z_num (* -1 ?spin_num))
                (while (<= ?spin_z_num ?spin_num) 
                    (assert
                        (SpinWave (unique_id ?*total_unique_id_counter*) 
                        (spin_num ?spin_num) (spin_denom ?spin_denom) (spin_z_num ?spin_z_num)
                        (isospin_num ?isospin_num) (isospin_denom ?isospin_denom) (isospin_z_num ?isospin_z_num)
                        (charge ?charge) (parity ?parity) (cparity ?cparity)
                        )
                    )
                    (bind ?*total_unique_id_counter* (+ ?*total_unique_id_counter* 1))
                    (bind ?spin_z_num (+ ?spin_z_num ?spin_denom))
                )
            )
            (bind ?isospin_z_num (+ ?isospin_z_num ?isospin_denom))
        )
    )

    )
    )
    )
)

Solution

  • The code fragment you posted doesn't consume a significant amount of memory (less than 17 MB). CLIPS uses the rete algorithm which saves the state of partial matches of rules, so it's likely that you have one or more rules matching the SpinWave facts that generate a large number of partial matches.

    You can use the matches command to get a snapshot of the number of partial matches used by each rule and the join-activity command for an overall snapshot of rules that might have performance issues.

    For example, after running the manners benchmarks I can see using these commands that the find_seating and make_path rules are the ones I should check for performance issues.

    CLIPS> (batch "manners128.bat")
    TRUE
    CLIPS> (clear)
    CLIPS> (unwatch compilations)
    CLIPS> (watch statistics)
    CLIPS> (set-strategy depth)
    depth
    CLIPS> (load manners.clp)
    :%%%%%%%********
    TRUE
    CLIPS> (reset)
    CLIPS> (load-facts manners128.fct)
    TRUE
    CLIPS> (run)
    8639 rules fired        Run time is 2.39572899999621 seconds.
    3606.00051174973 rules per second.
    4762 mean number of facts (8953 maximum).
    1 mean number of instances (1 maximum).
    138 mean number of activations (9490 maximum).
    CLIPS> 
    (progn$ 
       (?r (get-defrule-list)) 
       (printout t ?r ": " (join-activity ?r terse) crlf))
    assign_first_seat: (1314 1315 1315)
    find_seating: (5847660 7067211 7067211)
    make_path: (49549 16510 16510)
    path_done: (127 254 254)
    are_we_done: (128 255 255)
    continue: (0 127 127)
    print_results: (257 258 128)
    all_done: (0 1 0)
    CLIPS> 
    (progn$ 
       (?r (get-defrule-list)) 
       (printout t ?r ": " (matches ?r terse) crlf))
    assign_first_seat: (439 0 0)
    find_seating: (9260 0 0)
    make_path: (16256 0 0)
    path_done: (0 0 0)
    are_we_done: (129 0 0)
    continue: (0 0 0)
    print_results: (8258 129 0)
    all_done: (1 0 0)
    CLIPS> 
    

    Following up from your commment, here's one of the rule you mention causing the problem:

    (defrule check-charge 
       ?mymother <- (SpinWave (charge ?charge_mother)) 
       ?mydaughter1 <- (SpinWave (charge ?charge_daughter1)) 
       ?mydaughter2 <- (SpinWave (charge ?charge_daughter2)) 
       =>)
    

    None of the patterns have conditions which restrict the number of facts that will match the other patterns, so essentially this rules matches all combinations of three SpinWave facts. Since there are 144 facts matching each of the three patterns, there will be 2,985,984 (144 * 144 * 144) activations of the rule. So even if CLIPS were not generating partial matches for the first two patterns, there would still be millions of activations consuming memory that would eventually be released as each rule activation is allowed to execute.

    Without knowing what the rule is supposed to do or the relationship between the SpinWave facts it's difficult to be specific on how to make the rule more efficient, but generally you want patterns to be restricted in the number of facts that will match them by the variable bindings from the preceding patterns.

    So if there were a parent/child relationship between the facts, you can add and populate a parent slot for the SpinWave facts and use that to reduce the number of partial matches/activations generated:

    (defrule check-charge 
       ?mymother <- (SpinWave (charge ?charge_mother) (unique_id ?id)) 
       ?mydaughter1 <- (SpinWave (charge ?charge_daughter1) (parent ?id)) 
       ?mydaughter2 <- (SpinWave (charge ?charge_daughter2) (parent ?id)) 
       =>)