Giter VIP home page Giter VIP logo

Comments (11)

antonmedv avatar antonmedv commented on August 11, 2024

Hmm, I think this is due to cached regex.

from expr.

rucciva avatar rucciva commented on August 11, 2024

hi, i'am interested in using this library in my project, but it requires to store the expression into database.

i've created some benchmark regarding the marshalling process with comparison to govaluate performace and found that Unmarshaling from json takes as much time as re-compiling the script (though it allocated less bytes). Shouldn't vm.Program implement encoding.TextMarshaler instead and use faster serialisation method? for example using go codec for binary representation + base64 for string representation

import (
	"encoding/base64"
	"encoding/json"
	"testing"

	"github.com/antonmedv/expr"
	"github.com/antonmedv/expr/vm"
	"github.com/ugorji/go/codec"
	"gopkg.in/Knetic/govaluate.v3"
)

func BenchmarkLibrary(b *testing.B) {
	parameters := map[string]interface{}{
		"s_age": 10,
	}
	s := `s_age == 10 || s_age < 10`

	b.Run("expr", func(b *testing.B) {
		for n := 0; n < b.N; n++ {
			p, _ := expr.Compile(s, expr.Env(parameters))
			expr.Run(p, parameters)
		}
	})

	b.Run("expr-marshalled", func(b *testing.B) {
		p, _ := expr.Compile(s, expr.Env(parameters))
		j, _ := json.Marshal(p)
		for n := 0; n < b.N; n++ {
			uP := &vm.Program{}
			json.Unmarshal(j, &uP)
			expr.Run(uP, parameters)
		}
	})
	b.Run("expr-go-codec", func(b *testing.B) {
		p, _ := expr.Compile(s, expr.Env(parameters))
		var bh codec.BincHandle
		var data []byte
		enc := codec.NewEncoderBytes(&data, &bh)
		enc.Encode(p)
		s := base64.StdEncoding.EncodeToString(data)
		for n := 0; n < b.N; n++ {
			data2, _ := base64.StdEncoding.DecodeString(s)
			p2 := &vm.Program{}
			dec := codec.NewDecoderBytes(data2, &bh)
			dec.Decode(p2)
			expr.Run(p2, parameters)
		}
	})

	b.Run("govaluate", func(b *testing.B) {
		for n := 0; n < b.N; n++ {
			expression, _ := govaluate.NewEvaluableExpression(s)
			expression.Evaluate(parameters)
		}
	})
}
BenchmarkLibrary/expr-8         	   50000	     27376 ns/op	   10600 B/op	     222 allocs/op
BenchmarkLibrary/expr-marshalled-8         	   50000	     26022 ns/op	    3097 B/op	      97 allocs/op
BenchmarkLibrary/expr-go-codec-8           	  200000	      7798 ns/op	    2160 B/op	      23 allocs/op
BenchmarkLibrary/govaluate-8               	  300000	      4317 ns/op	    2136 B/op	      52 allocs/op

from expr.

antonmedv avatar antonmedv commented on August 11, 2024

Hi,

If you want to store a compiled program in DB why do you benchmark compiling and unmarshaling at the same time?

+	p, _ := expr.Compile(s, expr.Env(parameters))
+	j, _ := json.Marshal(p)
	b.Run("expr-marshalled", func(b *testing.B) {
-		p, _ := expr.Compile(s, expr.Env(parameters))
-		j, _ := json.Marshal(p)
		for n := 0; n < b.N; n++ {
			uP := &vm.Program{}
			json.Unmarshal(j, &uP)
			expr.Run(uP, parameters)
		}
	})

But this is really interesting. I think it's will be cool to implement fast marshal/unmarshal for a program. In my use case, we keep the program compiled in memory, so I never taked a look at speed on unmarshaling.

Currently, a program consists of these things:

type Program struct {
	Source    *file.Source       // Original source code
	Locations []file.Location // References of bytecodes to source  code location (used for showing code snippets on error)
	Constants []interface{}   // List of constants
	Bytecode  []byte              // Bytecode of a program itself.
}

Maybe you can create a PR with improvements?

from expr.

rucciva avatar rucciva commented on August 11, 2024

If you want to store a compiled program in DB why do you benchmark compiling and unmarshaling at the same time

Oops, my mistake, should have taken that out from run(). But i think it won't change the overall result .

I can create a PR, but i'm not confident with what binary encoding would be best. It also hasn't solve the regex problem too.

from expr.

antonmedv avatar antonmedv commented on August 11, 2024

Will be cool first, understand which part of a program slow to unmarshal (Sorce, Locations, Constants, Bytecode) and may be using encoding/gob for Constants will help?

from expr.

rucciva avatar rucciva commented on August 11, 2024

Depends on what will be stored in Constant (currently i haven't dig deeper on the source code). AFAIK encoding/gob will return error when a struct has no exported field.

from expr.

antonmedv avatar antonmedv commented on August 11, 2024

Constant usually it is int, string, vm.Call and some maps/slices.

from expr.

rucciva avatar rucciva commented on August 11, 2024

And the maps/slice are also int, string, and vm.Call ?
Just out of curiousity, is Constant related to the cached regex you've mention before?

Btw, What about Source?, this is the one that failed applying encoding/gob to vm.Program as a whole

from expr.

antonmedv avatar antonmedv commented on August 11, 2024

Yes, regex also stored in Constants (as we don't want to recompile regexp on each execution of a program). Usually map/slice stored in Constants are from expressions. For example, if you compile

foo in [1, 2, 3]

Constants will contain []int slice, so we don't need to recalculate it on program execution.

Source is just a string of source code with some chaced offest indexes. Fields can be made public to mage it available for gob.

from expr.

rucciva avatar rucciva commented on August 11, 2024

after adding encoding.BinaryMarshaller and encoding.BinaryUnmarshaller to Source, encoding with gob now success. But when you add regex, gob also fail

func TestProgram_Marshall(t *testing.T) {
	param := map[string]interface{}{
		"subject": map[string]interface{}{
			"age": 10,
		},
	}
	s := `subject.age > 10 && "hello" matches "hell"`
	p, err := Compile(s, Env(param))
	if err != nil {
		t.Error(err)
	}

	var buff bytes.Buffer
	enc := gob.NewEncoder(&buff)
	dec := gob.NewDecoder(&buff)

	if err := enc.Encode(p); err != nil {
		t.Error("encode error: ", err)
	}
	p2 := &vm.Program{}
	if err := dec.Decode(p2); err != nil {
		t.Error("decode error: ", err)
	}
	o, err := Run(p2, param)
	if err != nil {
		t.Error(err)
	}
	if v, ok := o.(bool); !ok || !v {
		t.Error("should match")
	}
}
encode error:  gob: type not registered for interface: regexp.Regexp

I think expr need to wrap regexp and implement encoding.BinaryMarshaller and encoding.BinaryUnmarshaller and use the String() value of regexp.Regexp

from expr.

antonmedv avatar antonmedv commented on August 11, 2024

Marshaling is not considered impossible in general case. As ConstExpr results also saved in constants.

from expr.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.