diff --git a/Makefile b/Makefile
index a5b3978109eefbc5d193bc9485e7cc35d2159c45..74e855fe6da4a94faae157c9f3c522244eeb2837 100644
--- a/Makefile
+++ b/Makefile
@@ -1894,6 +1894,7 @@ LIBGRPC_SRC = \
     src/core/iomgr/iomgr_windows.c \
     src/core/iomgr/pollset_kick.c \
     src/core/iomgr/pollset_multipoller_with_poll_posix.c \
+    src/core/iomgr/pollset_multipoller_with_epoll.c \
     src/core/iomgr/pollset_posix.c \
     src/core/iomgr/pollset_windows.c \
     src/core/iomgr/resolve_address.c \
@@ -2029,6 +2030,7 @@ src/core/iomgr/iomgr_posix.c: $(OPENSSL_DEP)
 src/core/iomgr/iomgr_windows.c: $(OPENSSL_DEP)
 src/core/iomgr/pollset_kick.c: $(OPENSSL_DEP)
 src/core/iomgr/pollset_multipoller_with_poll_posix.c: $(OPENSSL_DEP)
+src/core/iomgr/pollset_multipoller_with_epoll.c: $(OPENSSL_DEP)
 src/core/iomgr/pollset_posix.c: $(OPENSSL_DEP)
 src/core/iomgr/pollset_windows.c: $(OPENSSL_DEP)
 src/core/iomgr/resolve_address.c: $(OPENSSL_DEP)
@@ -2186,6 +2188,7 @@ objs/$(CONFIG)/src/core/iomgr/iomgr_posix.o:
 objs/$(CONFIG)/src/core/iomgr/iomgr_windows.o: 
 objs/$(CONFIG)/src/core/iomgr/pollset_kick.o: 
 objs/$(CONFIG)/src/core/iomgr/pollset_multipoller_with_poll_posix.o: 
+objs/$(CONFIG)/src/core/iomgr/pollset_multipoller_with_epoll.o: 
 objs/$(CONFIG)/src/core/iomgr/pollset_posix.o: 
 objs/$(CONFIG)/src/core/iomgr/pollset_windows.o: 
 objs/$(CONFIG)/src/core/iomgr/resolve_address.o: 
@@ -2427,6 +2430,7 @@ LIBGRPC_UNSECURE_SRC = \
     src/core/iomgr/iomgr_windows.c \
     src/core/iomgr/pollset_kick.c \
     src/core/iomgr/pollset_multipoller_with_poll_posix.c \
+    src/core/iomgr/pollset_multipoller_with_epoll.c \
     src/core/iomgr/pollset_posix.c \
     src/core/iomgr/pollset_windows.c \
     src/core/iomgr/resolve_address.c \
@@ -2567,6 +2571,7 @@ objs/$(CONFIG)/src/core/iomgr/iomgr_posix.o:
 objs/$(CONFIG)/src/core/iomgr/iomgr_windows.o: 
 objs/$(CONFIG)/src/core/iomgr/pollset_kick.o: 
 objs/$(CONFIG)/src/core/iomgr/pollset_multipoller_with_poll_posix.o: 
+objs/$(CONFIG)/src/core/iomgr/pollset_multipoller_with_epoll.o: 
 objs/$(CONFIG)/src/core/iomgr/pollset_posix.o: 
 objs/$(CONFIG)/src/core/iomgr/pollset_windows.o: 
 objs/$(CONFIG)/src/core/iomgr/resolve_address.o: 
diff --git a/build.json b/build.json
index ca41c8dcb3985029097cd1e40c33f0a6efda9f19..2347383b99381980d9757496c7f2060da151a20e 100644
--- a/build.json
+++ b/build.json
@@ -139,6 +139,7 @@
         "src/core/iomgr/iomgr_windows.c",
         "src/core/iomgr/pollset_kick.c",
         "src/core/iomgr/pollset_multipoller_with_poll_posix.c",
+        "src/core/iomgr/pollset_multipoller_with_epoll.c",
         "src/core/iomgr/pollset_posix.c",
         "src/core/iomgr/pollset_windows.c",
         "src/core/iomgr/resolve_address.c",
diff --git a/include/grpc/support/port_platform.h b/include/grpc/support/port_platform.h
index b0b528d28288aaf5ed4afe1c9410b0f4aefc9c8b..9d4bfbee5e83385355f409999b7487349195d940 100644
--- a/include/grpc/support/port_platform.h
+++ b/include/grpc/support/port_platform.h
@@ -73,7 +73,7 @@
 #define GPR_CPU_LINUX 1
 #define GPR_GCC_ATOMIC 1
 #define GPR_LINUX 1
-#define GPR_POSIX_MULTIPOLL_WITH_POLL 1
+#define GPR_LINUX_MULTIPOLL_WITH_EPOLL 1
 #define GPR_POSIX_WAKEUP_FD 1
 #define GPR_LINUX_EVENTFD 1
 #define GPR_POSIX_SOCKET 1
diff --git a/src/core/iomgr/pollset_multipoller_with_epoll.c b/src/core/iomgr/pollset_multipoller_with_epoll.c
new file mode 100644
index 0000000000000000000000000000000000000000..9fb28195062925e9de24d5829befd01519a6b08d
--- /dev/null
+++ b/src/core/iomgr/pollset_multipoller_with_epoll.c
@@ -0,0 +1,197 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * 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 Google Inc. 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
+ * OWNER 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 <grpc/support/port_platform.h>
+
+#ifdef GPR_LINUX_MULTIPOLL_WITH_EPOLL
+
+#include <errno.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <unistd.h>
+
+#include "src/core/iomgr/fd_posix.h"
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+typedef struct {
+  int epoll_fd;
+  grpc_wakeup_fd_info wakeup_fd;
+} pollset_hdr;
+
+static void multipoll_with_epoll_pollset_add_fd(grpc_pollset *pollset,
+                                                grpc_fd *fd) {
+  pollset_hdr *h = pollset->data.ptr;
+  struct epoll_event ev;
+  int err;
+
+  ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
+  ev.data.ptr = fd;
+  err = epoll_ctl(h->epoll_fd, EPOLL_CTL_ADD, fd->fd, &ev);
+  if (err < 0) {
+    /* FDs may be added to a pollset multiple times, so EEXIST is normal. */
+    if (errno != EEXIST) {
+      gpr_log(GPR_ERROR, "epoll_ctl add for %d failed: %s", fd->fd,
+              strerror(errno));
+    }
+  }
+}
+
+static void multipoll_with_epoll_pollset_del_fd(grpc_pollset *pollset,
+                                                grpc_fd *fd) {
+  pollset_hdr *h = pollset->data.ptr;
+  int err;
+  /* Note that this can race with concurrent poll, but that should be fine since
+   * at worst it creates a spurious read event on a reused grpc_fd object. */
+  err = epoll_ctl(h->epoll_fd, EPOLL_CTL_DEL, fd->fd, NULL);
+  if (err < 0) {
+    gpr_log(GPR_ERROR, "epoll_ctl del for %d failed: %s", fd->fd,
+            strerror(errno));
+  }
+}
+
+/* TODO(klempner): We probably want to turn this down a bit */
+#define GRPC_EPOLL_MAX_EVENTS 1000
+
+static int multipoll_with_epoll_pollset_maybe_work(
+    grpc_pollset *pollset, gpr_timespec deadline, gpr_timespec now,
+    int allow_synchronous_callback) {
+  struct epoll_event ep_ev[GRPC_EPOLL_MAX_EVENTS];
+  int ep_rv;
+  pollset_hdr *h = pollset->data.ptr;
+  int timeout_ms;
+
+  /* If you want to ignore epoll's ability to sanely handle parallel pollers,
+   * for a more apples-to-apples performance comparison with poll, add a
+   * if (pollset->counter == 0) { return 0 }
+   * here.
+   */
+
+  if (gpr_time_cmp(deadline, gpr_inf_future) == 0) {
+    timeout_ms = -1;
+  } else {
+    timeout_ms = gpr_time_to_millis(gpr_time_sub(deadline, now));
+    if (timeout_ms <= 0) {
+      return 1;
+    }
+  }
+  pollset->counter += 1;
+  gpr_mu_unlock(&pollset->mu);
+
+  do {
+    ep_rv = epoll_wait(h->epoll_fd, ep_ev, GRPC_EPOLL_MAX_EVENTS, timeout_ms);
+    if (ep_rv < 0) {
+      if (errno != EINTR) {
+        gpr_log(GPR_ERROR, "epoll_wait() failed: %s", strerror(errno));
+      }
+    } else {
+      int i;
+      for (i = 0; i < ep_rv; ++i) {
+        if (ep_ev[i].data.ptr == 0) {
+          grpc_wakeup_fd_consume_wakeup(&h->wakeup_fd);
+        } else {
+          grpc_fd *fd = ep_ev[i].data.ptr;
+          /* TODO(klempner): We might want to consider making err and pri
+           * separate events */
+          int cancel = ep_ev[i].events & (EPOLLERR | EPOLLHUP);
+          int read = ep_ev[i].events & (EPOLLIN | EPOLLPRI);
+          int write = ep_ev[i].events & EPOLLOUT;
+          if (read || cancel) {
+            grpc_fd_become_readable(fd, allow_synchronous_callback);
+          }
+          if (write || cancel) {
+            grpc_fd_become_writable(fd, allow_synchronous_callback);
+          }
+        }
+      }
+    }
+    timeout_ms = 0;
+  } while (ep_rv == GRPC_EPOLL_MAX_EVENTS);
+
+  gpr_mu_lock(&pollset->mu);
+  pollset->counter -= 1;
+  /* TODO(klempner): This should signal once per event rather than broadcast,
+   * although it probably doesn't matter because threads will generally be
+   * blocked in epoll_wait rather than being blocked on the cv. */
+  gpr_cv_broadcast(&pollset->cv);
+  return 1;
+}
+
+static void multipoll_with_epoll_pollset_destroy(grpc_pollset *pollset) {
+  pollset_hdr *h = pollset->data.ptr;
+  grpc_wakeup_fd_destroy(&h->wakeup_fd);
+  close(h->epoll_fd);
+  gpr_free(h);
+}
+
+static void epoll_kick(grpc_pollset *pollset) {
+  pollset_hdr *h = pollset->data.ptr;
+  grpc_wakeup_fd_wakeup(&h->wakeup_fd);
+}
+
+static const grpc_pollset_vtable multipoll_with_epoll_pollset = {
+    multipoll_with_epoll_pollset_add_fd, multipoll_with_epoll_pollset_del_fd,
+    multipoll_with_epoll_pollset_maybe_work, epoll_kick,
+    multipoll_with_epoll_pollset_destroy};
+
+void grpc_platform_become_multipoller(grpc_pollset *pollset, grpc_fd **fds,
+                                      size_t nfds) {
+  size_t i;
+  pollset_hdr *h = gpr_malloc(sizeof(pollset_hdr));
+  struct epoll_event ev;
+  int err;
+
+  pollset->vtable = &multipoll_with_epoll_pollset;
+  pollset->data.ptr = h;
+  h->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+  if (h->epoll_fd < 0) {
+    /* TODO(klempner): Fall back to poll here, especially on ENOSYS */
+    gpr_log(GPR_ERROR, "epoll_create1 failed: %s", strerror(errno));
+    abort();
+  }
+  for (i = 0; i < nfds; i++) {
+    multipoll_with_epoll_pollset_add_fd(pollset, fds[i]);
+  }
+
+  grpc_wakeup_fd_create(&h->wakeup_fd);
+  ev.events = EPOLLIN;
+  ev.data.ptr = 0;
+  err = epoll_ctl(h->epoll_fd, EPOLL_CTL_ADD,
+                  GRPC_WAKEUP_FD_GET_READ_FD(&h->wakeup_fd), &ev);
+  if (err < 0) {
+    gpr_log(GPR_ERROR, "Wakeup fd epoll_ctl failed: %s", strerror(errno));
+    abort();
+  }
+}
+
+#endif  /* GPR_LINUX_MULTIPOLL_WITH_EPOLL */
diff --git a/src/core/iomgr/pollset_multipoller_with_poll_posix.c b/src/core/iomgr/pollset_multipoller_with_poll_posix.c
index 3244ae08db57ada55006ae2ba3a8bc2fd004de09..c136ee0b5286450801df577fe47d33d8ce1d9c8a 100644
--- a/src/core/iomgr/pollset_multipoller_with_poll_posix.c
+++ b/src/core/iomgr/pollset_multipoller_with_poll_posix.c
@@ -200,6 +200,10 @@ static int multipoll_with_poll_pollset_maybe_work(
   return 1;
 }
 
+static void multipoll_with_poll_pollset_kick(grpc_pollset *p) {
+  grpc_pollset_kick_kick(&p->kick_state);
+}
+
 static void multipoll_with_poll_pollset_destroy(grpc_pollset *pollset) {
   size_t i;
   pollset_hdr *h = pollset->data.ptr;
@@ -219,7 +223,7 @@ static void multipoll_with_poll_pollset_destroy(grpc_pollset *pollset) {
 
 static const grpc_pollset_vtable multipoll_with_poll_pollset = {
     multipoll_with_poll_pollset_add_fd, multipoll_with_poll_pollset_del_fd,
-    multipoll_with_poll_pollset_maybe_work,
+    multipoll_with_poll_pollset_maybe_work, multipoll_with_poll_pollset_kick,
     multipoll_with_poll_pollset_destroy};
 
 void grpc_platform_become_multipoller(grpc_pollset *pollset, grpc_fd **fds,
diff --git a/src/core/iomgr/pollset_posix.c b/src/core/iomgr/pollset_posix.c
index 2837a0dff3ffd204809c6ffb4e5c4fd69e78e9ed..53c9806fb9bdee1d74cb289c24095963202d93bf 100644
--- a/src/core/iomgr/pollset_posix.c
+++ b/src/core/iomgr/pollset_posix.c
@@ -76,7 +76,7 @@ static void backup_poller(void *p) {
 
 void grpc_pollset_kick(grpc_pollset *p) {
   if (p->counter) {
-    grpc_pollset_kick_kick(&p->kick_state);
+    p->vtable->kick(p);
   }
 }
 
@@ -84,6 +84,10 @@ void grpc_pollset_force_kick(grpc_pollset *p) {
   grpc_pollset_kick_kick(&p->kick_state);
 }
 
+static void kick_using_pollset_kick(grpc_pollset *p) {
+  grpc_pollset_kick_kick(&p->kick_state);
+}
+
 /* global state management */
 
 grpc_pollset *grpc_backup_pollset(void) { return &g_backup_pollset; }
@@ -186,7 +190,7 @@ static void empty_pollset_destroy(grpc_pollset *pollset) {}
 
 static const grpc_pollset_vtable empty_pollset = {
     empty_pollset_add_fd, empty_pollset_del_fd, empty_pollset_maybe_work,
-    empty_pollset_destroy};
+    kick_using_pollset_kick, empty_pollset_destroy};
 
 static void become_empty_pollset(grpc_pollset *pollset) {
   pollset->vtable = &empty_pollset;
@@ -296,7 +300,8 @@ static void unary_poll_pollset_destroy(grpc_pollset *pollset) {
 
 static const grpc_pollset_vtable unary_poll_pollset = {
     unary_poll_pollset_add_fd, unary_poll_pollset_del_fd,
-    unary_poll_pollset_maybe_work, unary_poll_pollset_destroy};
+    unary_poll_pollset_maybe_work, kick_using_pollset_kick,
+    unary_poll_pollset_destroy};
 
 static void become_unary_pollset(grpc_pollset *pollset, grpc_fd *fd) {
   pollset->vtable = &unary_poll_pollset;
diff --git a/src/core/iomgr/pollset_posix.h b/src/core/iomgr/pollset_posix.h
index cdcb9951675d6d4d7668d7d6ef45cd0664cc6cd0..b1a82fccfe73ba562436110b5fe59989e2b862b9 100644
--- a/src/core/iomgr/pollset_posix.h
+++ b/src/core/iomgr/pollset_posix.h
@@ -66,6 +66,7 @@ struct grpc_pollset_vtable {
   void (*del_fd)(grpc_pollset *pollset, struct grpc_fd *fd);
   int (*maybe_work)(grpc_pollset *pollset, gpr_timespec deadline,
                     gpr_timespec now, int allow_synchronous_callback);
+  void (*kick)(grpc_pollset *pollset);
   void (*destroy)(grpc_pollset *pollset);
 };
 
diff --git a/test/core/iomgr/tcp_client_posix_test.c b/test/core/iomgr/tcp_client_posix_test.c
index 00b10f936480e7ec1e456babefe8556f77f05aa4..78709f47fbcb113975b8ff02aae0cab27c6f8874 100644
--- a/test/core/iomgr/tcp_client_posix_test.c
+++ b/test/core/iomgr/tcp_client_posix_test.c
@@ -171,6 +171,7 @@ void test_times_out(void) {
 int main(void) {
   grpc_iomgr_init();
   test_succeeds();
+  gpr_log(GPR_ERROR, "End of first test");
   test_fails();
   test_times_out();
   grpc_iomgr_shutdown();
diff --git a/vsprojects/vs2013/grpc.vcxproj b/vsprojects/vs2013/grpc.vcxproj
index bd87569a3f9309ff90231f19195bce9cc4a3fd5d..fc740fec925c78f9c3ae3f3fa3ddfb4f697b9763 100644
--- a/vsprojects/vs2013/grpc.vcxproj
+++ b/vsprojects/vs2013/grpc.vcxproj
@@ -273,6 +273,8 @@
     </ClCompile>
     <ClCompile Include="..\..\src\core\iomgr\pollset_multipoller_with_poll_posix.c">
     </ClCompile>
+    <ClCompile Include="..\..\src\core\iomgr\pollset_multipoller_with_epoll.c">
+    </ClCompile>
     <ClCompile Include="..\..\src\core\iomgr\pollset_posix.c">
     </ClCompile>
     <ClCompile Include="..\..\src\core\iomgr\pollset_windows.c">
diff --git a/vsprojects/vs2013/grpc.vcxproj.filters b/vsprojects/vs2013/grpc.vcxproj.filters
index fed8fb10bfb69f5e64a9360aa05ebdda301a41f5..75ecc7a822d5c46d99b9d36933613c3d47cfdb8b 100644
--- a/vsprojects/vs2013/grpc.vcxproj.filters
+++ b/vsprojects/vs2013/grpc.vcxproj.filters
@@ -130,6 +130,9 @@
     <ClCompile Include="..\..\src\core\iomgr\pollset_multipoller_with_poll_posix.c">
       <Filter>src\core\iomgr</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\core\iomgr\pollset_multipoller_with_epoll.c">
+      <Filter>src\core\iomgr</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\core\iomgr\pollset_posix.c">
       <Filter>src\core\iomgr</Filter>
     </ClCompile>
diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj b/vsprojects/vs2013/grpc_unsecure.vcxproj
index 0e5bdae62f3a2319b28d6a0f8cae5d085374c727..c5130eee5e789a65ee945a91061ecc51133711fa 100644
--- a/vsprojects/vs2013/grpc_unsecure.vcxproj
+++ b/vsprojects/vs2013/grpc_unsecure.vcxproj
@@ -235,6 +235,8 @@
     </ClCompile>
     <ClCompile Include="..\..\src\core\iomgr\pollset_multipoller_with_poll_posix.c">
     </ClCompile>
+    <ClCompile Include="..\..\src\core\iomgr\pollset_multipoller_with_epoll.c">
+    </ClCompile>
     <ClCompile Include="..\..\src\core\iomgr\pollset_posix.c">
     </ClCompile>
     <ClCompile Include="..\..\src\core\iomgr\pollset_windows.c">
diff --git a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters
index b0964b61b33c15cfd7a111cafedba0cce27d36e9..50f319066f145a1086b23eccb1f6dccb07e9264a 100644
--- a/vsprojects/vs2013/grpc_unsecure.vcxproj.filters
+++ b/vsprojects/vs2013/grpc_unsecure.vcxproj.filters
@@ -91,6 +91,9 @@
     <ClCompile Include="..\..\src\core\iomgr\pollset_multipoller_with_poll_posix.c">
       <Filter>src\core\iomgr</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\core\iomgr\pollset_multipoller_with_epoll.c">
+      <Filter>src\core\iomgr</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\core\iomgr\pollset_posix.c">
       <Filter>src\core\iomgr</Filter>
     </ClCompile>