Skip to content

Commit ca70b9a

Browse files
committed
Optimize most common binary expression evaluation cases
By testing the most common cases first we can avoid the pretty expensive overhead for the more complex cases. This adds a bit of code overhead trading for better performance.
1 parent 2bd96ac commit ca70b9a

File tree

2 files changed

+155
-26
lines changed

2 files changed

+155
-26
lines changed

src/eval.cpp

Lines changed: 152 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ namespace Sass {
5252
ctx(exp.ctx),
5353
force(false),
5454
is_in_comment(false)
55-
{ }
55+
{
56+
bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true);
57+
bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false);
58+
}
5659
Eval::~Eval() { }
5760

5861
Env* Eval::environment()
@@ -535,9 +538,138 @@ namespace Sass {
535538
Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in)
536539
{
537540

538-
String_Schema_Obj ret_schema;
541+
Expression_Obj lhs = b_in->left();
542+
Expression_Obj rhs = b_in->right();
543+
enum Sass_OP op_type = b_in->optype();
544+
545+
if (op_type == Sass_OP::AND) {
546+
// LOCAL_FLAG(force, true);
547+
lhs = lhs->perform(this);
548+
if (!*lhs) return lhs.detach();
549+
return rhs->perform(this);
550+
}
551+
else if (op_type == Sass_OP::OR) {
552+
// LOCAL_FLAG(force, true);
553+
lhs = lhs->perform(this);
554+
if (*lhs) return lhs.detach();
555+
return rhs->perform(this);
556+
}
557+
558+
// Evaluate variables as early o
559+
while (Variable_Ptr l_v = Cast<Variable>(lhs)) {
560+
lhs = operator()(l_v);
561+
}
562+
while (Variable_Ptr r_v = Cast<Variable>(rhs)) {
563+
rhs = operator()(r_v);
564+
}
565+
539566
Binary_Expression_Obj b = b_in;
540-
enum Sass_OP op_type = b->optype();
567+
568+
// Evaluate sub-expressions early on
569+
while (Binary_Expression_Ptr l_b = Cast<Binary_Expression>(lhs)) {
570+
if (!force && l_b->is_delayed()) break;
571+
lhs = operator()(l_b);
572+
}
573+
while (Binary_Expression_Ptr r_b = Cast<Binary_Expression>(rhs)) {
574+
if (!force && r_b->is_delayed()) break;
575+
rhs = operator()(r_b);
576+
}
577+
578+
// don't eval delayed expressions (the '/' when used as a separator)
579+
if (!force && op_type == Sass_OP::DIV && b->is_delayed()) {
580+
b->right(b->right()->perform(this));
581+
b->left(b->left()->perform(this));
582+
return b.detach();
583+
}
584+
585+
// specific types we know are final
586+
// handle them early to avoid overhead
587+
if (Number_Ptr l_n = Cast<Number>(lhs)) {
588+
// lhs is number and rhs is number
589+
if (Number_Ptr r_n = Cast<Number>(rhs)) {
590+
try {
591+
switch (op_type) {
592+
case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false;
593+
case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true;
594+
case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false;
595+
case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true;
596+
case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false;
597+
case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true;
598+
case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD:
599+
return op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate());
600+
default: break;
601+
}
602+
}
603+
catch (Exception::OperationError& err)
604+
{
605+
throw Exception::SassValueError(b_in->pstate(), err);
606+
}
607+
}
608+
// lhs is number and rhs is color
609+
else if (Color_Ptr r_c = Cast<Color>(rhs)) {
610+
try {
611+
switch (op_type) {
612+
case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false;
613+
case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true;
614+
case Sass_OP::LT: return *l_n < *r_c ? bool_true : bool_false;
615+
case Sass_OP::GTE: return *l_n < *r_c ? bool_false : bool_true;
616+
case Sass_OP::LTE: return *l_n < *r_c || *l_n == *r_c ? bool_true : bool_false;
617+
case Sass_OP::GT: return *l_n < *r_c || *l_n == *r_c ? bool_false : bool_true;
618+
case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD:
619+
return op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate());
620+
default: break;
621+
}
622+
}
623+
catch (Exception::OperationError& err)
624+
{
625+
throw Exception::SassValueError(b_in->pstate(), err);
626+
}
627+
}
628+
}
629+
else if (Color_Ptr l_c = Cast<Color>(lhs)) {
630+
// lhs is color and rhs is color
631+
if (Color_Ptr r_c = Cast<Color>(rhs)) {
632+
try {
633+
switch (op_type) {
634+
case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false;
635+
case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true;
636+
case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false;
637+
case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true;
638+
case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false;
639+
case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true;
640+
case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD:
641+
return op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate());
642+
default: break;
643+
}
644+
}
645+
catch (Exception::OperationError& err)
646+
{
647+
throw Exception::SassValueError(b_in->pstate(), err);
648+
}
649+
}
650+
// lhs is color and rhs is number
651+
else if (Number_Ptr r_n = Cast<Number>(rhs)) {
652+
try {
653+
switch (op_type) {
654+
case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false;
655+
case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true;
656+
case Sass_OP::LT: return *l_c < *r_n ? bool_true : bool_false;
657+
case Sass_OP::GTE: return *l_c < *r_n ? bool_false : bool_true;
658+
case Sass_OP::LTE: return *l_c < *r_n || *l_c == *r_n ? bool_true : bool_false;
659+
case Sass_OP::GT: return *l_c < *r_n || *l_c == *r_n ? bool_false : bool_true;
660+
case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD:
661+
return op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate());
662+
default: break;
663+
}
664+
}
665+
catch (Exception::OperationError& err)
666+
{
667+
throw Exception::SassValueError(b_in->pstate(), err);
668+
}
669+
}
670+
}
671+
672+
String_Schema_Obj ret_schema;
541673

542674
// only the last item will be used to eval the binary expression
543675
if (String_Schema_Ptr s_l = Cast<String_Schema>(b->left())) {
@@ -568,16 +700,6 @@ namespace Sass {
568700
}
569701
}
570702

571-
// don't eval delayed expressions (the '/' when used as a separator)
572-
if (!force && op_type == Sass_OP::DIV && b->is_delayed()) {
573-
b->right(b->right()->perform(this));
574-
b->left(b->left()->perform(this));
575-
return b.detach();
576-
}
577-
578-
Expression_Obj lhs = b->left();
579-
Expression_Obj rhs = b->right();
580-
581703
// fully evaluate their values
582704
if (op_type == Sass_OP::EQ ||
583705
op_type == Sass_OP::NEQ ||
@@ -598,19 +720,6 @@ namespace Sass {
598720
lhs = lhs->perform(this);
599721
}
600722

601-
Binary_Expression_Obj u3 = b;
602-
switch (op_type) {
603-
case Sass_OP::AND: {
604-
return *lhs ? b->right()->perform(this) : lhs.detach();
605-
}
606-
607-
case Sass_OP::OR: {
608-
return *lhs ? lhs.detach() : b->right()->perform(this);
609-
}
610-
611-
default:
612-
break;
613-
}
614723
// not a logical connective, so go ahead and eval the rhs
615724
rhs = rhs->perform(this);
616725
AST_Node_Obj lu = lhs;
@@ -1385,6 +1494,23 @@ namespace Sass {
13851494
throw Exception::ZeroDivisionError(l, r);
13861495
}
13871496

1497+
size_t l_n_units = l.numerator_units().size();
1498+
size_t l_d_units = l.numerator_units().size();
1499+
size_t r_n_units = r.denominator_units().size();
1500+
size_t r_d_units = r.denominator_units().size();
1501+
// optimize out the most common and simplest case
1502+
if (l_n_units == r_n_units && l_d_units == r_d_units) {
1503+
if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) {
1504+
if (l.numerator_units() == r.numerator_units()) {
1505+
if (l.denominator_units() == r.denominator_units()) {
1506+
Number_Ptr v = SASS_MEMORY_COPY(&l);
1507+
v->value(ops[op](lv, rv));
1508+
return v;
1509+
}
1510+
}
1511+
}
1512+
}
1513+
13881514
Number tmp(&r); // copy
13891515
bool strict = op != Sass_OP::MUL && op != Sass_OP::DIV;
13901516
tmp.normalize(l.find_convertible_unit(), strict);

src/eval.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ namespace Sass {
2626
bool force;
2727
bool is_in_comment;
2828

29+
Boolean_Obj bool_true;
30+
Boolean_Obj bool_false;
31+
2932
Env* environment();
3033
Backtrace* backtrace();
3134
Selector_List_Obj selector();

0 commit comments

Comments
 (0)