diff --git a/include/grpc/grpc.h b/include/grpc/grpc.h
index be4c4d2b176a8700cdb5199bda2eedc1a75b6abb..1cf5b5513b3365d66d7245ce41306abbc0dbbdc1 100644
--- a/include/grpc/grpc.h
+++ b/include/grpc/grpc.h
@@ -77,6 +77,12 @@ typedef enum {
   GRPC_ARG_POINTER
 } grpc_arg_type;
 
+typedef struct grpc_arg_pointer_vtable {
+  void *(*copy)(void *p);
+  void (*destroy)(void *p);
+  int (*cmp)(void *p, void *q);
+} grpc_arg_pointer_vtable;
+
 /** A single argument... each argument has a key and a value
 
     A note on naming keys:
@@ -97,8 +103,7 @@ typedef struct {
     int integer;
     struct {
       void *p;
-      void *(*copy)(void *p);
-      void (*destroy)(void *p);
+      const grpc_arg_pointer_vtable *vtable;
     } pointer;
   } value;
 } grpc_arg;
diff --git a/include/grpc/support/useful.h b/include/grpc/support/useful.h
index 9f08d788c0d5de2d7fcec406c4883831872157f3..003e096cf9a43f96ae1838fb1a44a64576a63da9 100644
--- a/include/grpc/support/useful.h
+++ b/include/grpc/support/useful.h
@@ -72,4 +72,6 @@
     0x0f0f0f0f) %                                \
    255)
 
+#define GPR_ICMP(a, b) ((a) < (b) ? -1 : ((a) > (b) ? 1 : 0))
+
 #endif /* GRPC_SUPPORT_USEFUL_H */
diff --git a/src/core/channel/channel_args.c b/src/core/channel/channel_args.c
index 487db1119a5bf33ab473ef35538ad05c5bc07961..cd35d2f701c430957afcb6a6c5d035d522027a1b 100644
--- a/src/core/channel/channel_args.c
+++ b/src/core/channel/channel_args.c
@@ -54,9 +54,7 @@ static grpc_arg copy_arg(const grpc_arg *src) {
       break;
     case GRPC_ARG_POINTER:
       dst.value.pointer = src->value.pointer;
-      dst.value.pointer.p = src->value.pointer.copy
-                                ? src->value.pointer.copy(src->value.pointer.p)
-                                : src->value.pointer.p;
+      dst.value.pointer.p = src->value.pointer.vtable->copy(src->value.pointer.p);
       break;
   }
   return dst;
@@ -93,6 +91,60 @@ grpc_channel_args *grpc_channel_args_merge(const grpc_channel_args *a,
   return grpc_channel_args_copy_and_add(a, b->args, b->num_args);
 }
 
+static int cmp_arg(const grpc_arg *a, const grpc_arg *b) {
+  int c = a->type - b->type;
+  if (c != 0) return c;
+  c = strcmp(a->key, b->key);
+  if (c != 0) return c;
+  switch (a->type) {
+    case GRPC_ARG_STRING:
+      c = strcmp(a->value.string, b->value.string);
+      break;
+    case GRPC_ARG_INTEGER:
+      c = GPR_ICMP(a->value.integer, b->value.integer);
+      break;
+    case GRPC_ARG_POINTER:
+      c = GPR_ICMP(a->value.pointer.p, 
+                   b->value.pointer.p);
+      if (c != 0) {
+        c = GPR_ICMP(a->value.pointer.vtable, 
+                     b->value.pointer.vtable);
+        if (c == 0) {
+          c = a->value.pointer.vtable->cmp(a->value.pointer.p,
+                                           b->value.pointer.p);
+        }
+      }
+      break;
+  }
+  return c;
+}
+
+static int cmp_key_stable(const void *ap, const void *bp) {
+  const grpc_arg *const *a = ap;
+  const grpc_arg *const *b = bp;
+  int c = strcmp((*a)->key, (*b)->key);
+  if (c == 0) c = GPR_ICMP(*a, *b);
+  return c;
+}
+
+grpc_channel_args *grpc_channel_args_normalize(const grpc_channel_args *a) {
+  grpc_arg **args = gpr_malloc(sizeof(grpc_arg*) * a->num_args);
+  for (size_t i = 0; i < a->num_args; i++) {
+    args[i] = &a->args[i];
+  }
+  qsort(args, a->num_args, sizeof(grpc_arg*), cmp_key_stable);
+
+  grpc_channel_args *b = gpr_malloc(sizeof(grpc_channel_args));
+  b->num_args = a->num_args;
+  b->args = gpr_malloc(sizeof(grpc_arg) * b->num_args);
+  for (size_t i = 0; i < a->num_args; i++) {
+    b->args[i] = copy_arg(args[i]);
+  }
+
+  gpr_free(args);
+  return b;
+}
+
 void grpc_channel_args_destroy(grpc_channel_args *a) {
   size_t i;
   for (i = 0; i < a->num_args; i++) {
@@ -103,9 +155,7 @@ void grpc_channel_args_destroy(grpc_channel_args *a) {
       case GRPC_ARG_INTEGER:
         break;
       case GRPC_ARG_POINTER:
-        if (a->args[i].value.pointer.destroy) {
-          a->args[i].value.pointer.destroy(a->args[i].value.pointer.p);
-        }
+        a->args[i].value.pointer.vtable->destroy(a->args[i].value.pointer.p);
         break;
     }
     gpr_free(a->args[i].key);
@@ -207,3 +257,14 @@ int grpc_channel_args_compression_algorithm_get_states(
     return (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1; /* All algs. enabled */
   }
 }
+
+int grpc_channel_args_compare(const grpc_channel_args *a, 
+                              const grpc_channel_args *b) {
+  int c = GPR_ICMP(a->num_args, b->num_args);
+  if (c != 0) return c;
+  for (size_t i = 0; i < a->num_args; i++) {
+    c = cmp_arg(&a->args[i], &b->args[i]);
+    if (c != 0) return c;
+  }
+  return 0;
+}
diff --git a/src/core/channel/channel_args.h b/src/core/channel/channel_args.h
index 480cc9aec2fc0e0ae4b4c0667e5816a1168974d3..ce848782e3f8afeec0cc1a70595b5a62ad2f125c 100644
--- a/src/core/channel/channel_args.h
+++ b/src/core/channel/channel_args.h
@@ -40,6 +40,9 @@
 /* Copy some arguments */
 grpc_channel_args *grpc_channel_args_copy(const grpc_channel_args *src);
 
+/* Copy some arguments, stably sorting keys */
+grpc_channel_args *grpc_channel_args_normalize(const grpc_channel_args *a);
+
 /** Copy some arguments and add the to_add parameter in the end.
    If to_add is NULL, it is equivalent to call grpc_channel_args_copy. */
 grpc_channel_args *grpc_channel_args_copy_and_add(const grpc_channel_args *src,
@@ -85,4 +88,6 @@ grpc_channel_args *grpc_channel_args_compression_algorithm_set_state(
 int grpc_channel_args_compression_algorithm_get_states(
     const grpc_channel_args *a);
 
+int grpc_channel_args_compare(const grpc_channel_args *a, const grpc_channel_args *b);
+
 #endif /* GRPC_INTERNAL_CORE_CHANNEL_CHANNEL_ARGS_H */
diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c
index 8b56c576458bbabd7073e850937fde9f76dfefa2..28f41b2041f23d94995ce28c4de18870fd70b6a3 100644
--- a/src/core/security/credentials.c
+++ b/src/core/security/credentials.c
@@ -196,14 +196,23 @@ static void *server_credentials_pointer_arg_copy(void *p) {
   return grpc_server_credentials_ref(p);
 }
 
+static int server_credentials_pointer_cmp(void *a, void *b) {
+  return GPR_ICMP(a, b);
+}
+
+static const grpc_arg_pointer_vtable cred_ptr_vtable = {
+  server_credentials_pointer_arg_copy,
+  server_credentials_pointer_arg_destroy,
+  server_credentials_pointer_cmp
+};
+
 grpc_arg grpc_server_credentials_to_arg(grpc_server_credentials *p) {
   grpc_arg arg;
   memset(&arg, 0, sizeof(grpc_arg));
   arg.type = GRPC_ARG_POINTER;
   arg.key = GRPC_SERVER_CREDENTIALS_ARG;
   arg.value.pointer.p = p;
-  arg.value.pointer.copy = server_credentials_pointer_arg_copy;
-  arg.value.pointer.destroy = server_credentials_pointer_arg_destroy;
+  arg.value.pointer.vtable = &cred_ptr_vtable;
   return arg;
 }
 
diff --git a/src/core/security/security_connector.c b/src/core/security/security_connector.c
index 61336a1057df088fc9bfcb740fe4e4de4628010c..40f486128b7d630350ccef9079c5031cd6cc493e 100644
--- a/src/core/security/security_connector.c
+++ b/src/core/security/security_connector.c
@@ -194,12 +194,21 @@ static void *connector_pointer_arg_copy(void *p) {
   return GRPC_SECURITY_CONNECTOR_REF(p, "connector_pointer_arg");
 }
 
+static int connector_pointer_cmp(void *a, void *b) {
+  return GPR_ICMP(a, b);
+}
+
+static const grpc_arg_pointer_vtable connector_pointer_vtable = {
+  connector_pointer_arg_copy,
+  connector_pointer_arg_destroy,
+  connector_pointer_cmp
+};
+
 grpc_arg grpc_security_connector_to_arg(grpc_security_connector *sc) {
   grpc_arg result;
   result.type = GRPC_ARG_POINTER;
   result.key = GRPC_SECURITY_CONNECTOR_ARG;
-  result.value.pointer.destroy = connector_pointer_arg_destroy;
-  result.value.pointer.copy = connector_pointer_arg_copy;
+  result.value.pointer.vtable = &connector_pointer_vtable;
   result.value.pointer.p = sc;
   return result;
 }
diff --git a/src/core/security/security_context.c b/src/core/security/security_context.c
index 2068c97d78c22328708a1dd06b43b38a36829a6f..6e948f61bc38fd1aace14511a74b66db8f69e1ed 100644
--- a/src/core/security/security_context.c
+++ b/src/core/security/security_context.c
@@ -309,14 +309,23 @@ static void *auth_context_pointer_arg_copy(void *p) {
   return GRPC_AUTH_CONTEXT_REF(p, "auth_context_pointer_arg");
 }
 
+static int auth_context_pointer_cmp(void *a, void *b) {
+  return GPR_ICMP(a, b);
+}
+
+static const grpc_arg_pointer_vtable auth_context_pointer_vtable = {
+  auth_context_pointer_arg_copy,
+  auth_context_pointer_arg_destroy,
+  auth_context_pointer_cmp
+};
+
 grpc_arg grpc_auth_context_to_arg(grpc_auth_context *p) {
   grpc_arg arg;
   memset(&arg, 0, sizeof(grpc_arg));
   arg.type = GRPC_ARG_POINTER;
   arg.key = GRPC_AUTH_CONTEXT_ARG;
   arg.value.pointer.p = p;
-  arg.value.pointer.copy = auth_context_pointer_arg_copy;
-  arg.value.pointer.destroy = auth_context_pointer_arg_destroy;
+  arg.value.pointer.vtable = &auth_context_pointer_vtable;
   return arg;
 }
 
diff --git a/src/cpp/common/channel_arguments.cc b/src/cpp/common/channel_arguments.cc
index 90cd5136af356ce2410da95e0959efee2e6d754e..3536e354287aafaf47275116048190a8f427e65b 100644
--- a/src/cpp/common/channel_arguments.cc
+++ b/src/cpp/common/channel_arguments.cc
@@ -62,9 +62,7 @@ ChannelArguments::ChannelArguments(const ChannelArguments& other)
         break;
       case GRPC_ARG_POINTER:
         ap.value.pointer = a->value.pointer;
-        ap.value.pointer.p = a->value.pointer.copy
-                                 ? a->value.pointer.copy(ap.value.pointer.p)
-                                 : ap.value.pointer.p;
+        ap.value.pointer.p = a->value.pointer.vtable->copy(ap.value.pointer.p);
         break;
     }
     args_.push_back(ap);
@@ -92,13 +90,27 @@ void ChannelArguments::SetInt(const grpc::string& key, int value) {
 }
 
 void ChannelArguments::SetPointer(const grpc::string& key, void* value) {
+  struct VtableMembers {
+    static void* Copy(void* in) { return in; }
+    static void Destroy(void* in) {}
+    static int Compare(void* a, void *b) {
+      if (a < b) return -1;
+      if (a > b) return 1;
+      return 0;
+    }
+  };
+  static const grpc_arg_pointer_vtable vtable = {
+    &VtableMembers::Copy,
+    &VtableMembers::Destroy,
+    &VtableMembers::Compare
+  };
+
   grpc_arg arg;
   arg.type = GRPC_ARG_POINTER;
   strings_.push_back(key);
   arg.key = const_cast<char*>(strings_.back().c_str());
   arg.value.pointer.p = value;
-  arg.value.pointer.copy = nullptr;
-  arg.value.pointer.destroy = nullptr;
+  arg.value.pointer.vtable = &vtable;
   args_.push_back(arg);
 }