diff --git a/src/core/lib/iomgr/error.c b/src/core/lib/iomgr/error.c
index cbaf9a42141837c49fbd6de5fcf689b404d61b01..7cdbe30198d8fb437b8ac7a2f29a2bdce9e59603 100644
--- a/src/core/lib/iomgr/error.c
+++ b/src/core/lib/iomgr/error.c
@@ -35,6 +35,7 @@
 
 #include <string.h>
 
+#include <grpc/slice.h>
 #include <grpc/status.h>
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
@@ -47,46 +48,7 @@
 
 #include "src/core/lib/iomgr/error_internal.h"
 #include "src/core/lib/profiling/timers.h"
-
-static void destroy_integer(void *key) {}
-
-static void *copy_integer(void *key) { return key; }
-
-static long compare_integers(void *key1, void *key2) {
-  return GPR_ICMP((uintptr_t)key1, (uintptr_t)key2);
-}
-
-static void destroy_string(void *str) { gpr_free(str); }
-
-static void *copy_string(void *str) { return gpr_strdup(str); }
-
-static void destroy_err(void *err) { GRPC_ERROR_UNREF(err); }
-
-static void *copy_err(void *err) { return GRPC_ERROR_REF(err); }
-
-static void destroy_time(void *tm) { gpr_free(tm); }
-
-static gpr_timespec *box_time(gpr_timespec tm) {
-  gpr_timespec *out = gpr_malloc(sizeof(*out));
-  *out = tm;
-  return out;
-}
-
-static void *copy_time(void *tm) { return box_time(*(gpr_timespec *)tm); }
-
-static const gpr_avl_vtable avl_vtable_ints = {destroy_integer, copy_integer,
-                                               compare_integers,
-                                               destroy_integer, copy_integer};
-
-static const gpr_avl_vtable avl_vtable_strs = {destroy_integer, copy_integer,
-                                               compare_integers, destroy_string,
-                                               copy_string};
-
-static const gpr_avl_vtable avl_vtable_times = {
-    destroy_integer, copy_integer, compare_integers, destroy_time, copy_time};
-
-static const gpr_avl_vtable avl_vtable_errs = {
-    destroy_integer, copy_integer, compare_integers, destroy_err, copy_err};
+#include "src/core/lib/slice/slice_internal.h"
 
 static const char *error_int_name(grpc_error_ints key) {
   switch (key) {
@@ -120,6 +82,8 @@ static const char *error_int_name(grpc_error_ints key) {
       return "limit";
     case GRPC_ERROR_INT_OCCURRED_DURING_WRITE:
       return "occurred_during_write";
+    case GRPC_ERROR_INT_MAX:
+      GPR_UNREACHABLE_CODE(return "unknown");
   }
   GPR_UNREACHABLE_CODE(return "unknown");
 }
@@ -150,6 +114,8 @@ static const char *error_str_name(grpc_error_strs key) {
       return "filename";
     case GRPC_ERROR_STR_QUEUED_BUFFERS:
       return "queued_buffers";
+    case GRPC_ERROR_STR_MAX:
+      GPR_UNREACHABLE_CODE(return "unknown");
   }
   GPR_UNREACHABLE_CODE(return "unknown");
 }
@@ -158,6 +124,8 @@ static const char *error_time_name(grpc_error_times key) {
   switch (key) {
     case GRPC_ERROR_TIME_CREATED:
       return "created";
+    case GRPC_ERROR_TIME_MAX:
+      GPR_UNREACHABLE_CODE(return "unknown");
   }
   GPR_UNREACHABLE_CODE(return "unknown");
 }
@@ -184,12 +152,36 @@ grpc_error *grpc_error_ref(grpc_error *err) {
 }
 #endif
 
+static void unref_errs(grpc_error *err) {
+  uint8_t slot = err->first_err;
+  while (slot != UINT8_MAX) {
+    grpc_linked_error *lerr = (grpc_linked_error *)(err->arena + slot);
+    GRPC_ERROR_UNREF(lerr->err);
+    GPR_ASSERT(err->last_err == slot ? lerr->next == UINT8_MAX
+                                     : lerr->next != UINT8_MAX);
+    slot = lerr->next;
+  }
+}
+
+static void unref_slice(grpc_slice slice) {
+  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
+  grpc_slice_unref_internal(&exec_ctx, slice);
+  grpc_exec_ctx_finish(&exec_ctx);
+}
+
+static void unref_strs(grpc_error *err) {
+  for (size_t which = 0; which < GRPC_ERROR_STR_MAX; ++which) {
+    uint8_t slot = err->strs[which];
+    if (slot != UINT8_MAX) {
+      unref_slice(*(grpc_slice *)(err->arena + slot));
+    }
+  }
+}
+
 static void error_destroy(grpc_error *err) {
   GPR_ASSERT(!grpc_error_is_special(err));
-  gpr_avl_unref(err->ints);
-  gpr_avl_unref(err->strs);
-  gpr_avl_unref(err->errs);
-  gpr_avl_unref(err->times);
+  unref_errs(err);
+  unref_strs(err);
   gpr_free((void *)gpr_atm_acq_load(&err->error_string));
   gpr_free(err);
 }
@@ -213,69 +205,189 @@ void grpc_error_unref(grpc_error *err) {
 }
 #endif
 
+static uint8_t get_placement(grpc_error **err, size_t size) {
+  GPR_ASSERT(*err);
+  uint8_t slots = (uint8_t)(size / sizeof(intptr_t));
+  if ((*err)->arena_size + slots > (*err)->arena_capacity) {
+    (*err)->arena_capacity = (uint8_t)(3 * (*err)->arena_capacity / 2);
+    *err = gpr_realloc(
+        *err, sizeof(grpc_error) + (*err)->arena_capacity * sizeof(intptr_t));
+  }
+  uint8_t placement = (*err)->arena_size;
+  (*err)->arena_size = (uint8_t)((*err)->arena_size + slots);
+  return placement;
+}
+
+static void internal_set_int(grpc_error **err, grpc_error_ints which,
+                             intptr_t value) {
+  // GPR_ASSERT((*err)->ints[which] == UINT8_MAX); // TODO, enforce this
+  uint8_t slot = (*err)->ints[which];
+  if (slot == UINT8_MAX) {
+    slot = get_placement(err, sizeof(value));
+  }
+  (*err)->ints[which] = slot;
+  (*err)->arena[slot] = value;
+}
+
+static void internal_set_str(grpc_error **err, grpc_error_strs which,
+                             grpc_slice value) {
+  // GPR_ASSERT((*err)->strs[which] == UINT8_MAX); // TODO, enforce this
+  uint8_t slot = (*err)->strs[which];
+  if (slot == UINT8_MAX) {
+    slot = get_placement(err, sizeof(value));
+  } else {
+    unref_slice(*(grpc_slice *)((*err)->arena + slot));
+  }
+  (*err)->strs[which] = slot;
+  memcpy((*err)->arena + slot, &value, sizeof(value));
+}
+
+static void internal_set_time(grpc_error **err, grpc_error_times which,
+                              gpr_timespec value) {
+  // GPR_ASSERT((*err)->times[which] == UINT8_MAX); // TODO, enforce this
+  uint8_t slot = (*err)->times[which];
+  if (slot == UINT8_MAX) {
+    slot = get_placement(err, sizeof(value));
+  }
+  (*err)->times[which] = slot;
+  memcpy((*err)->arena + slot, &value, sizeof(value));
+}
+
+static void internal_add_error(grpc_error **err, grpc_error *new) {
+  grpc_linked_error new_last = {new, UINT8_MAX};
+  uint8_t slot = get_placement(err, sizeof(grpc_linked_error));
+  if ((*err)->first_err == UINT8_MAX) {
+    GPR_ASSERT((*err)->last_err == UINT8_MAX);
+    (*err)->last_err = slot;
+    (*err)->first_err = slot;
+  } else {
+    GPR_ASSERT((*err)->last_err != UINT8_MAX);
+    grpc_linked_error *old_last =
+        (grpc_linked_error *)((*err)->arena + (*err)->last_err);
+    old_last->next = slot;
+    (*err)->last_err = slot;
+  }
+  memcpy((*err)->arena + slot, &new_last, sizeof(grpc_linked_error));
+}
+
+#define SLOTS_PER_INT (sizeof(intptr_t) / sizeof(intptr_t))
+#define SLOTS_PER_STR (sizeof(grpc_slice) / sizeof(intptr_t))
+#define SLOTS_PER_TIME (sizeof(gpr_timespec) / sizeof(intptr_t))
+#define SLOTS_PER_LINKED_ERROR (sizeof(grpc_linked_error) / sizeof(intptr_t))
+
+// size of storing one int and two slices and a timespec. For line, desc, file,
+// and time created
+#define DEFAULT_ERROR_CAPACITY \
+  (SLOTS_PER_INT + (SLOTS_PER_STR * 2) + SLOTS_PER_TIME)
+
+// It is very common to include and extra int and string in an error
+#define SURPLUS_CAPACITY (2 * SLOTS_PER_INT + SLOTS_PER_TIME)
+
 grpc_error *grpc_error_create(const char *file, int line, const char *desc,
                               grpc_error **referencing,
                               size_t num_referencing) {
   GPR_TIMER_BEGIN("grpc_error_create", 0);
-  grpc_error *err = gpr_malloc(sizeof(*err));
+  uint8_t initial_arena_capacity = (uint8_t)(
+      DEFAULT_ERROR_CAPACITY +
+      (uint8_t)(num_referencing * SLOTS_PER_LINKED_ERROR) + SURPLUS_CAPACITY);
+  grpc_error *err =
+      gpr_malloc(sizeof(*err) + initial_arena_capacity * sizeof(intptr_t));
   if (err == NULL) {  // TODO(ctiller): make gpr_malloc return NULL
     return GRPC_ERROR_OOM;
   }
 #ifdef GRPC_ERROR_REFCOUNT_DEBUG
   gpr_log(GPR_DEBUG, "%p create [%s:%d]", err, file, line);
 #endif
-  err->ints = gpr_avl_add(gpr_avl_create(&avl_vtable_ints),
-                          (void *)(uintptr_t)GRPC_ERROR_INT_FILE_LINE,
-                          (void *)(uintptr_t)line);
-  err->strs = gpr_avl_add(
-      gpr_avl_add(gpr_avl_create(&avl_vtable_strs),
-                  (void *)(uintptr_t)GRPC_ERROR_STR_FILE, gpr_strdup(file)),
-      (void *)(uintptr_t)GRPC_ERROR_STR_DESCRIPTION, gpr_strdup(desc));
-  err->errs = gpr_avl_create(&avl_vtable_errs);
-  err->next_err = 0;
-  for (size_t i = 0; i < num_referencing; i++) {
+
+  err->arena_size = 0;
+  err->arena_capacity = initial_arena_capacity;
+  err->first_err = UINT8_MAX;
+  err->last_err = UINT8_MAX;
+
+  memset(err->ints, UINT8_MAX, GRPC_ERROR_INT_MAX);
+  memset(err->strs, UINT8_MAX, GRPC_ERROR_STR_MAX);
+  memset(err->times, UINT8_MAX, GRPC_ERROR_TIME_MAX);
+
+  internal_set_int(&err, GRPC_ERROR_INT_FILE_LINE, line);
+  internal_set_str(&err, GRPC_ERROR_STR_FILE,
+                   grpc_slice_from_static_string(file));
+  internal_set_str(
+      &err, GRPC_ERROR_STR_DESCRIPTION,
+      grpc_slice_from_copied_buffer(
+          desc,
+          strlen(desc) +
+              1));  // TODO, pull this up.  // TODO(ncteisen), pull this up.
+
+  for (size_t i = 0; i < num_referencing; ++i) {
     if (referencing[i] == GRPC_ERROR_NONE) continue;
-    err->errs = gpr_avl_add(err->errs, (void *)(err->next_err++),
-                            GRPC_ERROR_REF(referencing[i]));
+    internal_add_error(
+        &err,
+        GRPC_ERROR_REF(
+            referencing[i]));  // TODO(ncteisen), change ownership semantics
   }
-  err->times = gpr_avl_add(gpr_avl_create(&avl_vtable_times),
-                           (void *)(uintptr_t)GRPC_ERROR_TIME_CREATED,
-                           box_time(gpr_now(GPR_CLOCK_REALTIME)));
+
+  internal_set_time(&err, GRPC_ERROR_TIME_CREATED, gpr_now(GPR_CLOCK_REALTIME));
+
   gpr_atm_no_barrier_store(&err->error_string, 0);
   gpr_ref_init(&err->refs, 1);
   GPR_TIMER_END("grpc_error_create", 0);
   return err;
 }
 
+static void ref_strs(grpc_error *err) {
+  for (size_t i = 0; i < GRPC_ERROR_STR_MAX; ++i) {
+    uint8_t slot = err->strs[i];
+    if (slot != UINT8_MAX) {
+      grpc_slice_ref_internal(*(grpc_slice *)(err->arena + slot));
+    }
+  }
+}
+
+static void ref_errs(grpc_error *err) {
+  uint8_t slot = err->first_err;
+  while (slot != UINT8_MAX) {
+    grpc_linked_error *lerr = (grpc_linked_error *)(err->arena + slot);
+    GRPC_ERROR_REF(lerr->err);
+    slot = lerr->next;
+  }
+}
+
 static grpc_error *copy_error_and_unref(grpc_error *in) {
   GPR_TIMER_BEGIN("copy_error_and_unref", 0);
   grpc_error *out;
   if (grpc_error_is_special(in)) {
-    if (in == GRPC_ERROR_NONE)
-      out = grpc_error_set_int(GRPC_ERROR_CREATE("no error"),
-                               GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_OK);
-    else if (in == GRPC_ERROR_OOM)
-      out = GRPC_ERROR_CREATE("oom");
-    else if (in == GRPC_ERROR_CANCELLED)
-      out =
-          grpc_error_set_int(GRPC_ERROR_CREATE("cancelled"),
-                             GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_CANCELLED);
-    else
-      out = GRPC_ERROR_CREATE("unknown");
+    out = GRPC_ERROR_CREATE("unknown");
+    if (in == GRPC_ERROR_NONE) {
+      internal_set_str(&out, GRPC_ERROR_STR_DESCRIPTION,
+                       grpc_slice_from_static_string("no error"));
+      internal_set_int(&out, GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_OK);
+    } else if (in == GRPC_ERROR_OOM) {
+      internal_set_str(&out, GRPC_ERROR_STR_DESCRIPTION,
+                       grpc_slice_from_static_string("oom"));
+    } else if (in == GRPC_ERROR_CANCELLED) {
+      internal_set_str(&out, GRPC_ERROR_STR_DESCRIPTION,
+                       grpc_slice_from_static_string("cancelled"));
+      internal_set_int(&out, GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_CANCELLED);
+    }
   } else if (gpr_ref_is_unique(&in->refs)) {
     out = in;
   } else {
-    out = gpr_malloc(sizeof(*out));
+    uint8_t new_arena_capacity = in->arena_capacity;
+    // the returned err will be added to, so we ensure this is room to avoid
+    // unneeded allocations.
+    if (in->arena_capacity - in->arena_size < (uint8_t)SLOTS_PER_STR) {
+      new_arena_capacity = (uint8_t)(3 * new_arena_capacity / 2);
+    }
+    out = gpr_malloc(sizeof(*in) + new_arena_capacity * sizeof(intptr_t));
 #ifdef GRPC_ERROR_REFCOUNT_DEBUG
     gpr_log(GPR_DEBUG, "%p create copying %p", out, in);
 #endif
-    out->ints = gpr_avl_ref(in->ints);
-    out->strs = gpr_avl_ref(in->strs);
-    out->errs = gpr_avl_ref(in->errs);
-    out->times = gpr_avl_ref(in->times);
+    memcpy(out, in, sizeof(*in) + in->arena_size * sizeof(intptr_t));
+    out->arena_capacity = new_arena_capacity;
     gpr_atm_no_barrier_store(&out->error_string, 0);
-    out->next_err = in->next_err;
     gpr_ref_init(&out->refs, 1);
+    ref_strs(out);
+    ref_errs(out);
     GRPC_ERROR_UNREF(in);
   }
   GPR_TIMER_END("copy_error_and_unref", 0);
@@ -286,7 +398,7 @@ grpc_error *grpc_error_set_int(grpc_error *src, grpc_error_ints which,
                                intptr_t value) {
   GPR_TIMER_BEGIN("grpc_error_set_int", 0);
   grpc_error *new = copy_error_and_unref(src);
-  new->ints = gpr_avl_add(new->ints, (void *)(uintptr_t)which, (void *)value);
+  internal_set_int(&new, which, value);
   GPR_TIMER_END("grpc_error_set_int", 0);
   return new;
 }
@@ -304,7 +416,6 @@ static special_error_status_map error_status_map[] = {
 
 bool grpc_error_get_int(grpc_error *err, grpc_error_ints which, intptr_t *p) {
   GPR_TIMER_BEGIN("grpc_error_get_int", 0);
-  void *pp;
   if (grpc_error_is_special(err)) {
     if (which == GRPC_ERROR_INT_GRPC_STATUS) {
       for (size_t i = 0; i < GPR_ARRAY_SIZE(error_status_map); i++) {
@@ -318,8 +429,9 @@ bool grpc_error_get_int(grpc_error *err, grpc_error_ints which, intptr_t *p) {
     GPR_TIMER_END("grpc_error_get_int", 0);
     return false;
   }
-  if (gpr_avl_maybe_get(err->ints, (void *)(uintptr_t)which, &pp)) {
-    if (p != NULL) *p = (intptr_t)pp;
+  uint8_t slot = err->ints[which];
+  if (slot != UINT8_MAX) {
+    if (p != NULL) *p = err->arena[slot];
     GPR_TIMER_END("grpc_error_get_int", 0);
     return true;
   }
@@ -331,8 +443,9 @@ grpc_error *grpc_error_set_str(grpc_error *src, grpc_error_strs which,
                                const char *value) {
   GPR_TIMER_BEGIN("grpc_error_set_str", 0);
   grpc_error *new = copy_error_and_unref(src);
-  new->strs =
-      gpr_avl_add(new->strs, (void *)(uintptr_t)which, gpr_strdup(value));
+  internal_set_str(&new, which,
+                   grpc_slice_from_copied_buffer(
+                       value, strlen(value) + 1));  // TODO, pull this up.
   GPR_TIMER_END("grpc_error_set_str", 0);
   return new;
 }
@@ -348,13 +461,19 @@ const char *grpc_error_get_str(grpc_error *err, grpc_error_strs which) {
     }
     return NULL;
   }
-  return gpr_avl_get(err->strs, (void *)(uintptr_t)which);
+  uint8_t slot = err->strs[which];
+  if (slot != UINT8_MAX) {
+    return (const char *)GRPC_SLICE_START_PTR(
+        *(grpc_slice *)(err->arena + slot));
+  } else {
+    return NULL;
+  }
 }
 
 grpc_error *grpc_error_add_child(grpc_error *src, grpc_error *child) {
   GPR_TIMER_BEGIN("grpc_error_add_child", 0);
   grpc_error *new = copy_error_and_unref(src);
-  new->errs = gpr_avl_add(new->errs, (void *)(new->next_err++), child);
+  internal_add_error(&new, child);
   GPR_TIMER_END("grpc_error_add_child", 0);
   return new;
 }
@@ -374,42 +493,6 @@ typedef struct {
   size_t cap_kvs;
 } kv_pairs;
 
-static void append_kv(kv_pairs *kvs, char *key, char *value) {
-  if (kvs->num_kvs == kvs->cap_kvs) {
-    kvs->cap_kvs = GPR_MAX(3 * kvs->cap_kvs / 2, 4);
-    kvs->kvs = gpr_realloc(kvs->kvs, sizeof(*kvs->kvs) * kvs->cap_kvs);
-  }
-  kvs->kvs[kvs->num_kvs].key = key;
-  kvs->kvs[kvs->num_kvs].value = value;
-  kvs->num_kvs++;
-}
-
-static void collect_kvs(gpr_avl_node *node, char *key(void *k),
-                        char *fmt(void *v), kv_pairs *kvs) {
-  if (node == NULL) return;
-  append_kv(kvs, key(node->key), fmt(node->value));
-  collect_kvs(node->left, key, fmt, kvs);
-  collect_kvs(node->right, key, fmt, kvs);
-}
-
-static char *key_int(void *p) {
-  return gpr_strdup(error_int_name((grpc_error_ints)(uintptr_t)p));
-}
-
-static char *key_str(void *p) {
-  return gpr_strdup(error_str_name((grpc_error_strs)(uintptr_t)p));
-}
-
-static char *key_time(void *p) {
-  return gpr_strdup(error_time_name((grpc_error_times)(uintptr_t)p));
-}
-
-static char *fmt_int(void *p) {
-  char *s;
-  gpr_asprintf(&s, "%" PRIdPTR, (intptr_t)p);
-  return s;
-}
-
 static void append_chr(char c, char **s, size_t *sz, size_t *cap) {
   if (*sz == *cap) {
     *cap = GPR_MAX(8, 3 * *cap / 2);
@@ -461,6 +544,40 @@ static void append_esc_str(const char *str, char **s, size_t *sz, size_t *cap) {
   append_chr('"', s, sz, cap);
 }
 
+static void append_kv(kv_pairs *kvs, char *key, char *value) {
+  if (kvs->num_kvs == kvs->cap_kvs) {
+    kvs->cap_kvs = GPR_MAX(3 * kvs->cap_kvs / 2, 4);
+    kvs->kvs = gpr_realloc(kvs->kvs, sizeof(*kvs->kvs) * kvs->cap_kvs);
+  }
+  kvs->kvs[kvs->num_kvs].key = key;
+  kvs->kvs[kvs->num_kvs].value = value;
+  kvs->num_kvs++;
+}
+
+static char *key_int(grpc_error_ints which) {
+  return gpr_strdup(error_int_name(which));
+}
+
+static char *fmt_int(intptr_t p) {
+  char *s;
+  gpr_asprintf(&s, "%" PRIdPTR, p);
+  return s;
+}
+
+static void collect_ints_kvs(grpc_error *err, kv_pairs *kvs) {
+  for (size_t which = 0; which < GRPC_ERROR_INT_MAX; ++which) {
+    uint8_t slot = err->ints[which];
+    if (slot != UINT8_MAX) {
+      append_kv(kvs, key_int((grpc_error_ints)which),
+                fmt_int(err->arena[slot]));
+    }
+  }
+}
+
+static char *key_str(grpc_error_strs which) {
+  return gpr_strdup(error_str_name(which));
+}
+
 static char *fmt_str(void *p) {
   char *s = NULL;
   size_t sz = 0;
@@ -470,8 +587,22 @@ static char *fmt_str(void *p) {
   return s;
 }
 
-static char *fmt_time(void *p) {
-  gpr_timespec tm = *(gpr_timespec *)p;
+static void collect_strs_kvs(grpc_error *err, kv_pairs *kvs) {
+  for (size_t which = 0; which < GRPC_ERROR_STR_MAX; ++which) {
+    uint8_t slot = err->strs[which];
+    if (slot != UINT8_MAX) {
+      append_kv(
+          kvs, key_str((grpc_error_strs)which),
+          fmt_str(GRPC_SLICE_START_PTR(*(grpc_slice *)(err->arena + slot))));
+    }
+  }
+}
+
+static char *key_time(grpc_error_times which) {
+  return gpr_strdup(error_time_name(which));
+}
+
+static char *fmt_time(gpr_timespec tm) {
   char *out;
   char *pfx = "!!";
   switch (tm.clock_type) {
@@ -492,24 +623,37 @@ static char *fmt_time(void *p) {
   return out;
 }
 
-static void add_errs(gpr_avl_node *n, char **s, size_t *sz, size_t *cap,
-                     bool *first) {
-  if (n == NULL) return;
-  add_errs(n->left, s, sz, cap, first);
-  if (!*first) append_chr(',', s, sz, cap);
-  *first = false;
-  const char *e = grpc_error_string(n->value);
-  append_str(e, s, sz, cap);
-  add_errs(n->right, s, sz, cap, first);
+static void collect_times_kvs(grpc_error *err, kv_pairs *kvs) {
+  for (size_t which = 0; which < GRPC_ERROR_TIME_MAX; ++which) {
+    uint8_t slot = err->times[which];
+    if (slot != UINT8_MAX) {
+      append_kv(kvs, key_time((grpc_error_times)which),
+                fmt_time(*(gpr_timespec *)(err->arena + slot)));
+    }
+  }
+}
+
+static void add_errs(grpc_error *err, char **s, size_t *sz, size_t *cap) {
+  uint8_t slot = err->first_err;
+  bool first = true;
+  while (slot != UINT8_MAX) {
+    grpc_linked_error *lerr = (grpc_linked_error *)(err->arena + slot);
+    if (!first) append_chr(',', s, sz, cap);
+    first = false;
+    const char *e = grpc_error_string(lerr->err);
+    append_str(e, s, sz, cap);
+    GPR_ASSERT(err->last_err == slot ? lerr->next == UINT8_MAX
+                                     : lerr->next != UINT8_MAX);
+    slot = lerr->next;
+  }
 }
 
 static char *errs_string(grpc_error *err) {
   char *s = NULL;
   size_t sz = 0;
   size_t cap = 0;
-  bool first = true;
   append_chr('[', &s, &sz, &cap);
-  add_errs(err->errs.root, &s, &sz, &cap, &first);
+  add_errs(err, &s, &sz, &cap);
   append_chr(']', &s, &sz, &cap);
   append_chr(0, &s, &sz, &cap);
   return s;
@@ -557,10 +701,10 @@ const char *grpc_error_string(grpc_error *err) {
   kv_pairs kvs;
   memset(&kvs, 0, sizeof(kvs));
 
-  collect_kvs(err->ints.root, key_int, fmt_int, &kvs);
-  collect_kvs(err->strs.root, key_str, fmt_str, &kvs);
-  collect_kvs(err->times.root, key_time, fmt_time, &kvs);
-  if (!gpr_avl_is_empty(err->errs)) {
+  collect_ints_kvs(err, &kvs);
+  collect_strs_kvs(err, &kvs);
+  collect_times_kvs(err, &kvs);
+  if (err->first_err != UINT8_MAX) {
     append_kv(&kvs, gpr_strdup("referenced_errors"), errs_string(err));
   }
 
diff --git a/src/core/lib/iomgr/error.h b/src/core/lib/iomgr/error.h
index 2613512acbeedd5970d64bb10a07e4cf1bb56c45..eb953947ae90805d776f3b315bc70822c4a740a3 100644
--- a/src/core/lib/iomgr/error.h
+++ b/src/core/lib/iomgr/error.h
@@ -102,6 +102,9 @@ typedef enum {
   GRPC_ERROR_INT_LIMIT,
   /// chttp2: did the error occur while a write was in progress
   GRPC_ERROR_INT_OCCURRED_DURING_WRITE,
+
+  /// Must always be last
+  GRPC_ERROR_INT_MAX,
 } grpc_error_ints;
 
 typedef enum {
@@ -129,11 +132,17 @@ typedef enum {
   GRPC_ERROR_STR_KEY,
   /// value associated with the error
   GRPC_ERROR_STR_VALUE,
+
+  /// Must always be last
+  GRPC_ERROR_STR_MAX,
 } grpc_error_strs;
 
 typedef enum {
   /// timestamp of error creation
   GRPC_ERROR_TIME_CREATED,
+
+  /// Must always be last
+  GRPC_ERROR_TIME_MAX,
 } grpc_error_times;
 
 /// The following "special" errors can be propagated without allocating memory.
@@ -184,8 +193,6 @@ void grpc_error_unref(grpc_error *err);
 grpc_error *grpc_error_set_int(grpc_error *src, grpc_error_ints which,
                                intptr_t value) GRPC_MUST_USE_RESULT;
 bool grpc_error_get_int(grpc_error *error, grpc_error_ints which, intptr_t *p);
-grpc_error *grpc_error_set_time(grpc_error *src, grpc_error_times which,
-                                gpr_timespec value) GRPC_MUST_USE_RESULT;
 grpc_error *grpc_error_set_str(grpc_error *src, grpc_error_strs which,
                                const char *value) GRPC_MUST_USE_RESULT;
 /// Returns NULL if the specified string is not set.
diff --git a/src/core/lib/iomgr/error_internal.h b/src/core/lib/iomgr/error_internal.h
index 1c89ead4ed66d53b36d81195bcccba2ba98ac5e7..fb4814e41f232e78cd6575ad29f2d94130123db4 100644
--- a/src/core/lib/iomgr/error_internal.h
+++ b/src/core/lib/iomgr/error_internal.h
@@ -35,18 +35,28 @@
 #define GRPC_CORE_LIB_IOMGR_ERROR_INTERNAL_H
 
 #include <inttypes.h>
-#include <stdbool.h>
+#include <stdbool.h>  // TODO, do we need this?
 
-#include <grpc/support/avl.h>
+#include <grpc/support/sync.h>
+
+typedef struct grpc_linked_error grpc_linked_error;
+
+struct grpc_linked_error {
+  grpc_error *err;
+  uint8_t next;
+};
 
 struct grpc_error {
   gpr_refcount refs;
-  gpr_avl ints;
-  gpr_avl strs;
-  gpr_avl times;
-  gpr_avl errs;
-  uintptr_t next_err;
+  uint8_t ints[GRPC_ERROR_INT_MAX];
+  uint8_t strs[GRPC_ERROR_STR_MAX];
+  uint8_t times[GRPC_ERROR_TIME_MAX];
+  uint8_t first_err;
+  uint8_t last_err;
   gpr_atm error_string;
+  uint8_t arena_size;
+  uint8_t arena_capacity;
+  intptr_t arena[0];
 };
 
 bool grpc_error_is_special(grpc_error *err);
diff --git a/src/core/lib/transport/error_utils.c b/src/core/lib/transport/error_utils.c
index da77828d9c11bb7ea9c13691d3711fe78de808c1..ef55e561fbface9d866b7f4d8a9e59c384dd773f 100644
--- a/src/core/lib/transport/error_utils.c
+++ b/src/core/lib/transport/error_utils.c
@@ -44,12 +44,12 @@ static grpc_error *recursively_find_error_with_field(grpc_error *error,
   }
   if (grpc_error_is_special(error)) return NULL;
   // Otherwise, search through its children.
-  intptr_t key = 0;
-  while (true) {
-    grpc_error *child_error = gpr_avl_get(error->errs, (void *)key++);
-    if (child_error == NULL) break;
-    grpc_error *result = recursively_find_error_with_field(child_error, which);
-    if (result != NULL) return result;
+  uint8_t slot = error->first_err;
+  while (slot != UINT8_MAX) {
+    grpc_linked_error *lerr = (grpc_linked_error *)(error->arena + slot);
+    grpc_error *result = recursively_find_error_with_field(lerr->err, which);
+    if (result) return result;
+    slot = lerr->next;
   }
   return NULL;
 }
@@ -112,13 +112,13 @@ bool grpc_error_has_clear_grpc_status(grpc_error *error) {
   if (grpc_error_get_int(error, GRPC_ERROR_INT_GRPC_STATUS, NULL)) {
     return true;
   }
-  intptr_t key = 0;
-  while (true) {
-    grpc_error *child_error = gpr_avl_get(error->errs, (void *)key++);
-    if (child_error == NULL) break;
-    if (grpc_error_has_clear_grpc_status(child_error)) {
+  uint8_t slot = error->first_err;
+  while (slot != UINT8_MAX) {
+    grpc_linked_error *lerr = (grpc_linked_error *)(error->arena + slot);
+    if (grpc_error_has_clear_grpc_status(lerr->err)) {
       return true;
     }
+    slot = lerr->next;
   }
   return false;
 }
diff --git a/test/core/iomgr/error_test.c b/test/core/iomgr/error_test.c
index 0452e4576a49ef69e65f1300db852dfef52e0f8a..2a6b1b17fdef595aeb96e4a8e2b88c2e3e01a419 100644
--- a/test/core/iomgr/error_test.c
+++ b/test/core/iomgr/error_test.c
@@ -1,6 +1,6 @@
 /*
  *
- * Copyright 2015, Google Inc.
+ * Copyright 2017, Google Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -118,14 +118,86 @@ static void test_copy_and_unref() {
   GRPC_ERROR_UNREF(error3);
 }
 
-static void print_error_strings() {
+static void test_create_referencing() {
+  grpc_error* child = grpc_error_set_str(
+      GRPC_ERROR_CREATE("Child"), GRPC_ERROR_STR_GRPC_MESSAGE, "message");
+  grpc_error* parent = GRPC_ERROR_CREATE_REFERENCING("Parent", &child, 1);
+  GPR_ASSERT(parent);
+
+  GRPC_ERROR_UNREF(child);
+  GRPC_ERROR_UNREF(parent);
+}
+
+static void test_create_referencing_many() {
+  grpc_error* children[3];
+  children[0] = grpc_error_set_str(GRPC_ERROR_CREATE("Child1"),
+                                   GRPC_ERROR_STR_GRPC_MESSAGE, "message");
+  children[1] = grpc_error_set_int(GRPC_ERROR_CREATE("Child2"),
+                                   GRPC_ERROR_INT_HTTP2_ERROR, 5);
+  children[2] = grpc_error_set_str(GRPC_ERROR_CREATE("Child3"),
+                                   GRPC_ERROR_STR_GRPC_MESSAGE, "message 3");
+
+  grpc_error* parent = GRPC_ERROR_CREATE_REFERENCING("Parent", children, 3);
+  GPR_ASSERT(parent);
+
+  for (size_t i = 0; i < 3; ++i) {
+    GRPC_ERROR_UNREF(children[i]);
+  }
+  GRPC_ERROR_UNREF(parent);
+}
+
+static void print_error_string() {
   grpc_error* error =
       grpc_error_set_int(GRPC_ERROR_CREATE("Error"), GRPC_ERROR_INT_GRPC_STATUS,
                          GRPC_STATUS_UNIMPLEMENTED);
-  error = grpc_error_set_int(error, GRPC_ERROR_INT_GRPC_STATUS, 0);
   error = grpc_error_set_int(error, GRPC_ERROR_INT_SIZE, 666);
   error = grpc_error_set_str(error, GRPC_ERROR_STR_GRPC_MESSAGE, "message");
-  gpr_log(GPR_DEBUG, "%s", grpc_error_string(error));
+  // gpr_log(GPR_DEBUG, "%s", grpc_error_string(error));
+  GRPC_ERROR_UNREF(error);
+}
+
+static void print_error_string_reference() {
+  grpc_error* children[2];
+  children[0] = grpc_error_set_str(
+      grpc_error_set_int(GRPC_ERROR_CREATE("1"), GRPC_ERROR_INT_GRPC_STATUS,
+                         GRPC_STATUS_UNIMPLEMENTED),
+      GRPC_ERROR_STR_GRPC_MESSAGE, "message for child 1");
+  children[1] = grpc_error_set_str(
+      grpc_error_set_int(GRPC_ERROR_CREATE("2sd"), GRPC_ERROR_INT_GRPC_STATUS,
+                         GRPC_STATUS_INTERNAL),
+      GRPC_ERROR_STR_GRPC_MESSAGE, "message for child 2");
+
+  grpc_error* parent = GRPC_ERROR_CREATE_REFERENCING("Parent", children, 2);
+
+  gpr_log(GPR_DEBUG, "%s", grpc_error_string(parent));
+
+  for (size_t i = 0; i < 2; ++i) {
+    GRPC_ERROR_UNREF(children[i]);
+  }
+  GRPC_ERROR_UNREF(parent);
+}
+
+static void test_os_error() {
+  int fake_errno = 5;
+  const char* syscall = "syscall name";
+  grpc_error* error = GRPC_OS_ERROR(fake_errno, syscall);
+
+  intptr_t i = 0;
+  GPR_ASSERT(grpc_error_get_int(error, GRPC_ERROR_INT_ERRNO, &i));
+  GPR_ASSERT(i == fake_errno);
+
+  const char* c = grpc_error_get_str(error, GRPC_ERROR_STR_SYSCALL);
+  GPR_ASSERT(c);
+  GPR_ASSERT(!strcmp(c, syscall));
+  GRPC_ERROR_UNREF(error);
+}
+
+static void test_special() {
+  grpc_error* error = GRPC_ERROR_NONE;
+  error = grpc_error_add_child(error, GRPC_ERROR_CREATE("test child"));
+  intptr_t i;
+  GPR_ASSERT(grpc_error_get_int(error, GRPC_ERROR_INT_GRPC_STATUS, &i));
+  GPR_ASSERT(i == GRPC_STATUS_OK);
   GRPC_ERROR_UNREF(error);
 }
 
@@ -135,7 +207,12 @@ int main(int argc, char** argv) {
   test_set_get_int();
   test_set_get_str();
   test_copy_and_unref();
-  print_error_strings();
+  print_error_string();
+  print_error_string_reference();
+  test_os_error();
+  test_create_referencing();
+  test_create_referencing_many();
+  test_special();
   grpc_shutdown();
 
   return 0;
diff --git a/test/cpp/microbenchmarks/bm_error.cc b/test/cpp/microbenchmarks/bm_error.cc
index ff1519b1f4ebfcab40f93f6a11746ec57a075565..c4f6aa19d5be95a6db342fc50f0a864e45382a13 100644
--- a/test/cpp/microbenchmarks/bm_error.cc
+++ b/test/cpp/microbenchmarks/bm_error.cc
@@ -72,32 +72,38 @@ static void BM_ErrorCreateAndSetStatus(benchmark::State& state) {
 BENCHMARK(BM_ErrorCreateAndSetStatus);
 
 static void BM_ErrorCreateAndSetIntAndStr(benchmark::State& state) {
+  TrackCounters track_counters;
   while (state.KeepRunning()) {
     GRPC_ERROR_UNREF(grpc_error_set_str(
         grpc_error_set_int(GRPC_ERROR_CREATE("GOAWAY received"),
                            GRPC_ERROR_INT_HTTP2_ERROR, (intptr_t)0),
         GRPC_ERROR_STR_RAW_BYTES, "raw bytes"));
   }
+  track_counters.Finish(state);
 }
 BENCHMARK(BM_ErrorCreateAndSetIntAndStr);
 
 static void BM_ErrorCreateAndSetIntLoop(benchmark::State& state) {
+  TrackCounters track_counters;
   grpc_error* error = GRPC_ERROR_CREATE("Error");
   int n = 0;
   while (state.KeepRunning()) {
     error = grpc_error_set_int(error, GRPC_ERROR_INT_GRPC_STATUS, n++);
   }
   GRPC_ERROR_UNREF(error);
+  track_counters.Finish(state);
 }
 BENCHMARK(BM_ErrorCreateAndSetIntLoop);
 
 static void BM_ErrorCreateAndSetStrLoop(benchmark::State& state) {
+  TrackCounters track_counters;
   grpc_error* error = GRPC_ERROR_CREATE("Error");
   const char* str = "hello";
   while (state.KeepRunning()) {
     error = grpc_error_set_str(error, GRPC_ERROR_STR_GRPC_MESSAGE, str);
   }
   GRPC_ERROR_UNREF(error);
+  track_counters.Finish(state);
 }
 BENCHMARK(BM_ErrorCreateAndSetStrLoop);