55import com .launchdarkly .sdk .LDUser ;
66import com .launchdarkly .sdk .LDValue ;
77import com .launchdarkly .sdk .LDValueType ;
8+ import com .launchdarkly .sdk .server .DataModel .Clause ;
89import com .launchdarkly .sdk .server .DataModel .FeatureFlag ;
10+ import com .launchdarkly .sdk .server .DataModel .Operator ;
11+ import com .launchdarkly .sdk .server .DataModel .Prerequisite ;
12+ import com .launchdarkly .sdk .server .DataModel .Rollout ;
913import com .launchdarkly .sdk .server .DataModel .Rule ;
14+ import com .launchdarkly .sdk .server .DataModel .Segment ;
15+ import com .launchdarkly .sdk .server .DataModel .SegmentRule ;
16+ import com .launchdarkly .sdk .server .DataModel .Target ;
1017import com .launchdarkly .sdk .server .DataModel .VariationOrRollout ;
1118import com .launchdarkly .sdk .server .DataModel .WeightedVariation ;
1219import com .launchdarkly .sdk .server .DataModelPreprocessing .ClausePreprocessed ;
2229/**
2330 * Encapsulates the feature flag evaluation logic. The Evaluator has no knowledge of the rest of the SDK environment;
2431 * if it needs to retrieve flags or segments that are referenced by a flag, it does so through a read-only interface
25- * that is provided in the constructor. It also produces feature requests as appropriate for any referenced prerequisite
26- * flags, but does not send them .
32+ * that is provided in the constructor. It also produces evaluation records (to be used in event data) as appropriate
33+ * for any referenced prerequisite flags .
2734 */
2835class Evaluator {
36+ //
37+ // IMPLEMENTATION NOTES ABOUT THIS FILE
38+ //
39+ // Flag evaluation is the hottest code path in the SDK; large applications may evaluate flags at a VERY high
40+ // volume, so every little bit of optimization we can achieve here could add up to quite a bit of overhead we
41+ // are not making the customer incur. Strategies that are used here for that purpose include:
42+ //
43+ // 1. Whenever possible, we are reusing precomputed instances of EvalResult; see DataModelPreprocessing and
44+ // EvaluatorHelpers.
45+ //
46+ // 2. If prerequisite evaluations happen as a side effect of an evaluation, rather than building and returning
47+ // a list of these, we deliver them one at a time via the PrerequisiteEvaluationSink callback mechanism.
48+ //
49+ // 3. If there's a piece of state that needs to be tracked across multiple methods during an evaluation, and
50+ // it's not feasible to just pass it as a method parameter, consider adding it as a field in the mutable
51+ // EvaluatorState object (which we will always have one of) rather than creating a new object to contain it.
52+ //
53+ // 4. Whenever possible, avoid using "for (variable: list)" here because it always creates an iterator object.
54+ // Instead, use the tedious old "get the size, iterate with a counter" approach.
55+ //
56+ // 5. Avoid using lambdas/closures here, because these generally cause a heap object to be allocated for
57+ // variables captured in the closure each time they are used.
58+ //
59+
2960 private final static Logger logger = Loggers .EVALUATION ;
3061
3162 /**
@@ -43,8 +74,8 @@ class Evaluator {
4374 * and simplifies testing.
4475 */
4576 static interface Getters {
46- DataModel . FeatureFlag getFlag (String key );
47- DataModel . Segment getSegment (String key );
77+ FeatureFlag getFlag (String key );
78+ Segment getSegment (String key );
4879 BigSegmentStoreWrapper .BigSegmentsQueryResult getBigSegments (String key );
4980 }
5081
@@ -82,7 +113,7 @@ private static class EvaluatorState {
82113 * @param eventFactory produces feature request events
83114 * @return an {@link EvalResult} - guaranteed non-null
84115 */
85- EvalResult evaluate (DataModel . FeatureFlag flag , LDUser user , PrerequisiteEvaluationSink prereqEvals ) {
116+ EvalResult evaluate (FeatureFlag flag , LDUser user , PrerequisiteEvaluationSink prereqEvals ) {
86117 if (flag .getKey () == INVALID_FLAG_KEY_THAT_THROWS_EXCEPTION ) {
87118 throw EXPECTED_EXCEPTION_FROM_INVALID_FLAG ;
88119 }
@@ -105,7 +136,7 @@ EvalResult evaluate(DataModel.FeatureFlag flag, LDUser user, PrerequisiteEvaluat
105136 return result ;
106137 }
107138
108- private EvalResult evaluateInternal (DataModel . FeatureFlag flag , LDUser user ,
139+ private EvalResult evaluateInternal (FeatureFlag flag , LDUser user ,
109140 PrerequisiteEvaluationSink prereqEvals , EvaluatorState state ) {
110141 if (!flag .isOn ()) {
111142 return EvaluatorHelpers .offResult (flag );
@@ -117,15 +148,20 @@ private EvalResult evaluateInternal(DataModel.FeatureFlag flag, LDUser user,
117148 }
118149
119150 // Check to see if targets match
120- for (DataModel .Target target : flag .getTargets ()) { // getTargets() and getValues() are guaranteed non-null
121- if (target .getValues ().contains (user .getKey ())) {
151+ List <Target > targets = flag .getTargets (); // guaranteed non-null
152+ int nTargets = targets .size ();
153+ for (int i = 0 ; i < nTargets ; i ++) {
154+ Target target = targets .get (i );
155+ if (target .getValues ().contains (user .getKey ())) { // getValues() is guaranteed non-null
122156 return EvaluatorHelpers .targetMatchResult (flag , target );
123157 }
124158 }
159+
125160 // Now walk through the rules and see if any match
126- List <DataModel .Rule > rules = flag .getRules (); // guaranteed non-null
127- for (int i = 0 ; i < rules .size (); i ++) {
128- DataModel .Rule rule = rules .get (i );
161+ List <Rule > rules = flag .getRules (); // guaranteed non-null
162+ int nRules = rules .size ();
163+ for (int i = 0 ; i < nRules ; i ++) {
164+ Rule rule = rules .get (i );
129165 if (ruleMatchesUser (flag , rule , user , state )) {
130166 return computeRuleMatch (flag , user , rule , i );
131167 }
@@ -138,11 +174,14 @@ private EvalResult evaluateInternal(DataModel.FeatureFlag flag, LDUser user,
138174
139175 // Checks prerequisites if any; returns null if successful, or an EvalResult if we have to
140176 // short-circuit due to a prerequisite failure.
141- private EvalResult checkPrerequisites (DataModel . FeatureFlag flag , LDUser user ,
177+ private EvalResult checkPrerequisites (FeatureFlag flag , LDUser user ,
142178 PrerequisiteEvaluationSink prereqEvals , EvaluatorState state ) {
143- for (DataModel .Prerequisite prereq : flag .getPrerequisites ()) { // getPrerequisites() is guaranteed non-null
179+ List <Prerequisite > prerequisites = flag .getPrerequisites (); // guaranteed non-null
180+ int nPrerequisites = prerequisites .size ();
181+ for (int i = 0 ; i < nPrerequisites ; i ++) {
182+ Prerequisite prereq = prerequisites .get (i );
144183 boolean prereqOk = true ;
145- DataModel . FeatureFlag prereqFeatureFlag = getters .getFlag (prereq .getKey ());
184+ FeatureFlag prereqFeatureFlag = getters .getFlag (prereq .getKey ());
146185 if (prereqFeatureFlag == null ) {
147186 logger .error ("Could not retrieve prerequisite flag \" {}\" when evaluating \" {}\" " , prereq .getKey (), flag .getKey ());
148187 prereqOk = false ;
@@ -177,11 +216,14 @@ private static EvalResult getValueForVariationOrRollout(
177216 if (maybeVariation != null ) {
178217 variation = maybeVariation .intValue ();
179218 } else {
180- DataModel . Rollout rollout = vr .getRollout ();
219+ Rollout rollout = vr .getRollout ();
181220 if (rollout != null && !rollout .getVariations ().isEmpty ()) {
182221 float bucket = bucketUser (rollout .getSeed (), user , flag .getKey (), rollout .getBucketBy (), flag .getSalt ());
183222 float sum = 0F ;
184- for (DataModel .WeightedVariation wv : rollout .getVariations ()) {
223+ List <WeightedVariation > variations = rollout .getVariations (); // guaranteed non-null
224+ int nVariations = variations .size ();
225+ for (int i = 0 ; i < nVariations ; i ++) {
226+ WeightedVariation wv = variations .get (i );
185227 sum += (float ) wv .getWeight () / 100000F ;
186228 if (bucket < sum ) {
187229 variation = wv .getVariation ();
@@ -224,22 +266,28 @@ private static EvaluationReason experimentize(EvaluationReason reason) {
224266 return reason ;
225267 }
226268
227- private boolean ruleMatchesUser (DataModel .FeatureFlag flag , DataModel .Rule rule , LDUser user , EvaluatorState state ) {
228- for (DataModel .Clause clause : rule .getClauses ()) { // getClauses() is guaranteed non-null
269+ private boolean ruleMatchesUser (FeatureFlag flag , Rule rule , LDUser user , EvaluatorState state ) {
270+ List <Clause > clauses = rule .getClauses (); // guaranteed non-null
271+ int nClauses = clauses .size ();
272+ for (int i = 0 ; i < nClauses ; i ++) {
273+ Clause clause = clauses .get (i );
229274 if (!clauseMatchesUser (clause , user , state )) {
230275 return false ;
231276 }
232277 }
233278 return true ;
234279 }
235280
236- private boolean clauseMatchesUser (DataModel . Clause clause , LDUser user , EvaluatorState state ) {
281+ private boolean clauseMatchesUser (Clause clause , LDUser user , EvaluatorState state ) {
237282 // In the case of a segment match operator, we check if the user is in any of the segments,
238283 // and possibly negate
239- if (clause .getOp () == DataModel .Operator .segmentMatch ) {
240- for (LDValue j : clause .getValues ()) {
241- if (j .isString ()) {
242- DataModel .Segment segment = getters .getSegment (j .stringValue ());
284+ if (clause .getOp () == Operator .segmentMatch ) {
285+ List <LDValue > values = clause .getValues (); // guaranteed non-null
286+ int nValues = values .size ();
287+ for (int i = 0 ; i < nValues ; i ++) {
288+ LDValue clauseValue = values .get (i );
289+ if (clauseValue .isString ()) {
290+ Segment segment = getters .getSegment (clauseValue .stringValue ());
243291 if (segment != null ) {
244292 if (segmentMatchesUser (segment , user , state )) {
245293 return maybeNegate (clause , true );
@@ -253,14 +301,16 @@ private boolean clauseMatchesUser(DataModel.Clause clause, LDUser user, Evaluato
253301 return clauseMatchesUserNoSegments (clause , user );
254302 }
255303
256- private boolean clauseMatchesUserNoSegments (DataModel . Clause clause , LDUser user ) {
304+ private boolean clauseMatchesUserNoSegments (Clause clause , LDUser user ) {
257305 LDValue userValue = user .getAttribute (clause .getAttribute ());
258306 if (userValue .isNull ()) {
259307 return false ;
260308 }
261309
262310 if (userValue .getType () == LDValueType .ARRAY ) {
263- for (LDValue value : userValue .values ()) {
311+ int nValues = userValue .size ();
312+ for (int i = 0 ; i < nValues ; i ++) {
313+ LDValue value = userValue .get (i );
264314 if (value .getType () == LDValueType .ARRAY || value .getType () == LDValueType .OBJECT ) {
265315 logger .error ("Invalid custom attribute value in user object for user key \" {}\" : {}" , user .getKey (), value );
266316 return false ;
@@ -278,11 +328,11 @@ private boolean clauseMatchesUserNoSegments(DataModel.Clause clause, LDUser user
278328 return false ;
279329 }
280330
281- static boolean clauseMatchAny (DataModel . Clause clause , LDValue userValue ) {
282- DataModel . Operator op = clause .getOp ();
331+ static boolean clauseMatchAny (Clause clause , LDValue userValue ) {
332+ Operator op = clause .getOp ();
283333 if (op != null ) {
284334 ClausePreprocessed preprocessed = clause .preprocessed ;
285- if (op == DataModel . Operator .in ) {
335+ if (op == Operator .in ) {
286336 // see if we have precomputed a Set for fast equality matching
287337 Set <LDValue > vs = preprocessed == null ? null : preprocessed .valuesSet ;
288338 if (vs != null ) {
@@ -305,11 +355,11 @@ static boolean clauseMatchAny(DataModel.Clause clause, LDValue userValue) {
305355 return false ;
306356 }
307357
308- private boolean maybeNegate (DataModel . Clause clause , boolean b ) {
358+ private boolean maybeNegate (Clause clause , boolean b ) {
309359 return clause .isNegate () ? !b : b ;
310360 }
311361
312- private boolean segmentMatchesUser (DataModel . Segment segment , LDUser user , EvaluatorState state ) {
362+ private boolean segmentMatchesUser (Segment segment , LDUser user , EvaluatorState state ) {
313363 String userKey = user .getKey (); // we've already verified that the key is non-null at the top of evaluate()
314364 if (segment .isUnbounded ()) {
315365 if (segment .getGeneration () == null ) {
@@ -346,16 +396,22 @@ private boolean segmentMatchesUser(DataModel.Segment segment, LDUser user, Evalu
346396 return false ;
347397 }
348398 }
349- for (DataModel .SegmentRule rule : segment .getRules ()) {
399+ List <SegmentRule > rules = segment .getRules (); // guaranteed non-null
400+ int nRules = rules .size ();
401+ for (int i = 0 ; i < nRules ; i ++) {
402+ SegmentRule rule = rules .get (i );
350403 if (segmentRuleMatchesUser (rule , user , segment .getKey (), segment .getSalt ())) {
351404 return true ;
352405 }
353406 }
354407 return false ;
355408 }
356409
357- private boolean segmentRuleMatchesUser (DataModel .SegmentRule segmentRule , LDUser user , String segmentKey , String salt ) {
358- for (DataModel .Clause c : segmentRule .getClauses ()) {
410+ private boolean segmentRuleMatchesUser (SegmentRule segmentRule , LDUser user , String segmentKey , String salt ) {
411+ List <Clause > clauses = segmentRule .getClauses (); // guaranteed non-null
412+ int nClauses = clauses .size ();
413+ for (int i = 0 ; i < nClauses ; i ++) {
414+ Clause c = clauses .get (i );
359415 if (!clauseMatchesUserNoSegments (c , user )) {
360416 return false ;
361417 }
@@ -380,7 +436,7 @@ private static EvalResult computeRuleMatch(FeatureFlag flag, LDUser user, Rule r
380436 return getValueForVariationOrRollout (flag , rule , user , null , reason );
381437 }
382438
383- static String makeBigSegmentRef (DataModel . Segment segment ) {
439+ static String makeBigSegmentRef (Segment segment ) {
384440 return String .format ("%s.g%d" , segment .getKey (), segment .getGeneration ());
385441 }
386442}
0 commit comments