Permalink
Browse files

added tests

  • Loading branch information...
Peter Ohler
Peter Ohler committed Feb 24, 2012
1 parent 8f6bf30 commit 2fd7af9585a371a17a8adba5d85775f9e68f7398
Showing with 426 additions and 161 deletions.
  1. +148 −0 ext/oj/cache.c
  2. +44 −0 ext/oj/cache.h
  3. +30 −0 ext/oj/oj.c
  4. +5 −0 ext/oj/oj.h
  5. +21 −3 notes
  6. +91 −0 test/perf.rb
  7. +59 −147 test/perf_obj.rb
  8. +28 −11 test/sample/file.rb
View
@@ -0,0 +1,148 @@
+/* cache.c
+ * Copyright (c) 2011, Peter Ohler
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of Peter Ohler nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "cache.h"
+
+struct _Cache {
+ char *key; // only set if the node has a value, and it is not an exact match
+ VALUE value;
+ struct _Cache *slots[16];
+};
+
+static void slot_print(Cache cache, unsigned int depth);
+
+void
+oj_cache_new(Cache *cache) {
+ if (0 == (*cache = (Cache)malloc(sizeof(struct _Cache)))) {
+ rb_raise(rb_eNoMemError, "not enough memory\n");
+ }
+ (*cache)->key = 0;
+ (*cache)->value = Qundef;
+ bzero((*cache)->slots, sizeof((*cache)->slots));
+}
+
+VALUE
+oj_cache_get(Cache cache, const char *key, VALUE **slot) {
+ unsigned char *k = (unsigned char*)key;
+ Cache *cp;
+
+ for (; '\0' != *k; k++) {
+ cp = cache->slots + (unsigned int)(*k >> 4); // upper 4 bits
+ if (0 == *cp) {
+ oj_cache_new(cp);
+ }
+ cache = *cp;
+ cp = cache->slots + (unsigned int)(*k & 0x0F); // lower 4 bits
+ if (0 == *cp) {
+ oj_cache_new(cp);
+ cache = *cp;
+ cache->key = ('\0' == *(k + 1)) ? 0 : strdup(key);
+ break;
+ } else {
+ cache = *cp;
+ if (Qundef != cache->value && 0 != cache->key) {
+ unsigned char *ck = (unsigned char*)(cache->key + (unsigned int)(k - (unsigned char*)key + 1));
+
+ if (0 == strcmp((char*)ck, (char*)(k + 1))) {
+ break;
+ } else {
+ Cache *cp2 = cp;
+
+ // if value was set along with the key then there are no slots filled yet
+ cp2 = (*cp2)->slots + (*ck >> 4);
+ oj_cache_new(cp2);
+ cp2 = (*cp2)->slots + (*ck & 0x0F);
+ oj_cache_new(cp2);
+ if ('\0' == *(ck + 1)) {
+ free(cache->key);
+ } else {
+ (*cp2)->key = cache->key;
+ }
+ (*cp2)->value = cache->value;
+ cache->key = 0;
+ cache->value = Qundef;
+ }
+ }
+ }
+ }
+ *slot = &cache->value;
+
+ return cache->value;
+}
+
+void
+oj_cache_print(Cache cache) {
+ //printf("-------------------------------------------\n");
+ slot_print(cache, 0);
+}
+
+static void
+slot_print(Cache c, unsigned int depth) {
+ char indent[256];
+ Cache *cp;
+ unsigned int i;
+
+ if (sizeof(indent) - 1 < depth) {
+ depth = ((int)sizeof(indent) - 1);
+ }
+ memset(indent, ' ', depth);
+ indent[depth] = '\0';
+ for (i = 0, cp = c->slots; i < 16; i++, cp++) {
+ if (0 == *cp) {
+ //printf("%s%02u:\n", indent, i);
+ } else {
+ if (0 == (*cp)->key && Qundef == (*cp)->value) {
+ printf("%s%02u:\n", indent, i);
+ } else {
+ const char *key = (0 == (*cp)->key) ? "*" : (*cp)->key;
+ const char *vs;
+ const char *clas;
+
+ if (Qundef == (*cp)->value) {
+ vs = "undefined";
+ clas = "";
+ } else {
+ VALUE rs = rb_funcall2((*cp)->value, rb_intern("to_s"), 0, 0);
+
+ vs = StringValuePtr(rs);
+ clas = rb_class2name(rb_obj_class((*cp)->value));
+ }
+ printf("%s%02u: %s = %s (%s)\n", indent, i, key, vs, clas);
+ }
+ slot_print(*cp, depth + 2);
+ }
+ }
+}
View
@@ -0,0 +1,44 @@
+/* cache.h
+ * Copyright (c) 2011, Peter Ohler
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of Peter Ohler nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __OJ_CACHE_H__
+#define __OJ_CACHE_H__
+
+#include "ruby.h"
+
+typedef struct _Cache *Cache;
+
+extern void oj_cache_new(Cache *cache);
+
+extern VALUE oj_cache_get(Cache cache, const char *key, VALUE **slot);
+
+extern void oj_cache_print(Cache cache);
+
+#endif /* __OJ_CACHE_H__ */
View
@@ -60,6 +60,9 @@ static VALUE null_sym;
static VALUE object_sym;
static VALUE strict_sym;
+Cache oj_class_cache = 0;
+Cache oj_attr_cache = 0;
+
static struct _Options default_options = {
{ '\0' }, // encoding
0, // indent
@@ -312,6 +315,29 @@ dump(int argc, VALUE *argv, VALUE self) {
return rstr;
}
+
+/* call-seq: to_file(file_path, obj, options)
+ *
+ * Dumps an Object to the specified file.
+ * @param [String] file_path file path to write the JSON document to
+ * @param [Object] obj Object to serialize as an JSON document String
+ * @param [Hash] options formating options
+ * @param [Fixnum] :indent format expected
+ * @param [true|false] :circular allow circular references, default: false
+ */
+static VALUE
+to_file(int argc, VALUE *argv, VALUE self) {
+ struct _Options copts = default_options;
+
+ if (3 == argc) {
+ parse_options(argv[2], &copts);
+ }
+ Check_Type(*argv, T_STRING);
+ oj_write_obj_to_file(argv[1], StringValuePtr(*argv), &copts);
+
+ return Qnil;
+}
+
void Init_oj() {
VALUE keep = Qnil;
@@ -324,6 +350,7 @@ void Init_oj() {
rb_define_module_function(Oj, "load", load_str, -1);
rb_define_module_function(Oj, "load_file", load_file, -1);
rb_define_module_function(Oj, "dump", dump, -1);
+ rb_define_module_function(Oj, "to_file", to_file, -1);
oj_instance_variables_id = rb_intern("instance_variables");
oj_to_hash_id = rb_intern("to_hash");
@@ -341,6 +368,9 @@ void Init_oj() {
strict_sym = ID2SYM(rb_intern("strict")); rb_ary_push(keep, strict_sym);
default_options.mode = ObjectMode;
+
+ oj_cache_new(&oj_class_cache);
+ oj_cache_new(&oj_attr_cache);
}
void
View
@@ -45,6 +45,8 @@ extern "C" {
#include "ruby/encoding.h"
#endif
+#include "cache.h"
+
#ifdef JRUBY
#define NO_RSTRUCT 1
#endif
@@ -96,6 +98,9 @@ extern ID oj_to_json_id;
extern ID oj_tv_sec_id;
extern ID oj_tv_usec_id;
+extern Cache oj_class_cache;
+extern Cache oj_attr_cache;
+
#if defined(__cplusplus)
#if 0
{ /* satisfy cc-mode */
View
24 notes
@@ -6,9 +6,25 @@
- next
- implement load mode
- - objects
- - hash
-
+ - add class and attr cache
+ - test
+ - rename simple.rb to tests.rb
+ - perf_obj
+ - add msgpack
+ - compare all gems, not just one
+ - maybe a matrix - nah
+ - ox oj marshal
+ *ox - 1.1 3.0
+ oj 0.9 - 2.5
+ marshal 0.3 0.3 -
+
+
+ - Perf
+ - for each
+ - title
+ - Proc
+ -
+
- stream
- load
- dump
@@ -23,3 +39,5 @@
- support circular object encoding
+- Monitoring
+
View
@@ -0,0 +1,91 @@
+
+class Perf
+
+ def initialize()
+ @items = []
+ end
+
+ def add(title, op, &blk)
+ @items << Item.new(title, op, &blk)
+ end
+
+ def run(iter)
+ base = Item.new(nil, nil) { }
+ base.run(iter, 0.0)
+ @items.each do |i|
+ i.run(iter, base.duration)
+ if i.error.nil?
+ puts "#{i.title}.#{i.op} #{iter} times in %0.3f seconds or %0.3f #{i.op}/sec." % [i.duration, iter / i.duration]
+ else
+ puts "***** #{i.title}.#{i.op} failed! #{i.error}"
+ end
+ end
+ summary()
+ end
+
+ def summary()
+ fastest = nil
+ slowest = nil
+ width = 6
+ @items.each do |i|
+ next if i.duration.nil?
+ width = i.title.size if width < i.title.size
+ end
+ iva = @items.clone
+ iva.delete_if { |i| i.duration.nil? }
+ iva.sort_by! { |i| i.duration }
+ puts
+ puts "Summary:"
+ puts "%*s time (secs) rate (ops/sec)" % [width, 'System']
+ puts "#{'-' * width} ----------- --------------"
+ iva.each do |i|
+ if i.duration.nil?
+ else
+ puts "%*s %11.3f %14.3f" % [width, i.title, i.duration, i.rate ]
+ end
+ end
+ puts
+ puts "Comparison Matrix\n(performance factor, 2.0 row is means twice as fast as column)"
+ puts ([' ' * width] + iva.map { |i| "%*s" % [width, i.title] }).join(' ')
+ puts (['-' * width] + iva.map { |i| '-' * width }).join(' ')
+ iva.each do |i|
+ line = ["%*s" % [width, i.title]]
+ iva.each do |o|
+ line << "%*.2f" % [width, o.duration / i.duration]
+ end
+ puts line.join(' ')
+ end
+ puts
+ end
+
+ class Item
+ attr_accessor :title
+ attr_accessor :op
+ attr_accessor :blk
+ attr_accessor :duration
+ attr_accessor :rate
+ attr_accessor :error
+
+ def initialize(title, op, &blk)
+ @title = title
+ @blk = blk
+ @op = op
+ @duration = nil
+ @rate = nil
+ @error = nil
+ end
+
+ def run(iter, base)
+ begin
+ start = Time.now
+ iter.times { @blk.call }
+ @duration = Time.now - start - base
+ @duration = 0.0 if @duration < 0.0
+ @rate = iter / @duration
+ rescue Exception => e
+ @error = "#{e.class}: #{e.message}"
+ end
+ end
+
+ end # Item
+end # Perf
Oops, something went wrong.

0 comments on commit 2fd7af9

Please sign in to comment.