diff --git a/mod/box/box.h b/mod/box/box.h
index d54a7868dbcb3f0e6590bcdc7868b55950f59062..7fb12efb31165d1b8dc5444be3f35782488b61ef 100644
--- a/mod/box/box.h
+++ b/mod/box/box.h
@@ -29,12 +29,12 @@
  * SUCH DAMAGE.
  */
 #include <util.h>
-@class Port;
 struct txn;
 struct tbuf;
+struct port;
 
 typedef void (*box_process_func)(struct txn *,
-				 Port *, u32, struct tbuf *);
+				 struct port *, u32, struct tbuf *);
 extern box_process_func box_process;
 
 #endif /* INCLUDES_TARANTOOL_BOX_H */
diff --git a/mod/box/box.m b/mod/box/box.m
index 44714c6acbcc67eeaf4e9ff2b9eabfdcf1e4ad6d..34afed24bea60af791da6bd9b44da2a85a12c85a 100644
--- a/mod/box/box.m
+++ b/mod/box/box.m
@@ -48,13 +48,13 @@
 #include "request.h"
 #include "txn.h"
 
-static void box_process_replica(struct txn *txn, Port *port,
+static void box_process_replica(struct txn *txn, struct port *port,
 				u32 op, struct tbuf *request_data);
-static void box_process_ro(struct txn *txn, Port *port,
+static void box_process_ro(struct txn *txn, struct port *port,
 			   u32 op, struct tbuf *request_data);
-static void box_process_ro(struct txn *txn, Port *port,
+static void box_process_ro(struct txn *txn, struct port *port,
 			   u32 op, struct tbuf *request_data);
-static void box_process_rw(struct txn *txn, Port *port,
+static void box_process_rw(struct txn *txn, struct port *port,
 			   u32 op, struct tbuf *request_data);
 box_process_func box_process = box_process_ro;
 
@@ -78,7 +78,7 @@ box_snap_row(const struct tbuf *t)
 }
 
 static void
-box_process_rw(struct txn *txn, Port *port,
+box_process_rw(struct txn *txn, struct port *port,
 	       u32 op, struct tbuf *data)
 {
 	ev_tstamp start = ev_now(), stop;
@@ -102,7 +102,7 @@ box_process_rw(struct txn *txn, Port *port,
 }
 
 static void
-box_process_replica(struct txn *txn, Port *port,
+box_process_replica(struct txn *txn, struct port *port,
 	       u32 op, struct tbuf *request_data)
 {
 	if (!request_is_select(op)) {
@@ -114,7 +114,7 @@ box_process_replica(struct txn *txn, Port *port,
 }
 
 static void
-box_process_ro(struct txn *txn, Port *port,
+box_process_ro(struct txn *txn, struct port *port,
 	       u32 op, struct tbuf *request_data)
 {
 	if (!request_is_select(op)) {
@@ -128,13 +128,13 @@ box_process_ro(struct txn *txn, Port *port,
 static void
 iproto_primary_port_handler(u32 op, struct tbuf *request_data)
 {
-	box_process(txn_begin(), port_iproto, op, request_data);
+	box_process(txn_begin(), &port_iproto, op, request_data);
 }
 
 static void
 iproto_secondary_port_handler(u32 op, struct tbuf *request_data)
 {
-	box_process_ro(txn_begin(), port_iproto, op, request_data);
+	box_process_ro(txn_begin(), &port_iproto, op, request_data);
 }
 
 static void
@@ -301,7 +301,7 @@ recover_row(struct tbuf *t)
 			u16 op = read_u16(t);
 			struct txn *txn = txn_begin();
 			txn->txn_flags |= BOX_NOT_STORE;
-			box_process_rw(txn, port_null, op, t);
+			box_process_rw(txn, &port_null, op, t);
 		} else {
 			say_error("unknown row tag: %i", (int)tag);
 			return -1;
@@ -468,7 +468,6 @@ mod_free(void)
 {
 	space_free();
 	memcached_free();
-	port_free();
 }
 
 void
@@ -477,8 +476,6 @@ mod_init(void)
 	title("loading");
 	atexit(mod_free);
 
-	port_init();
-
 	/* initialization spaces */
 	space_init();
 	/* configure memcached space */
diff --git a/mod/box/box_lua.h b/mod/box/box_lua.h
index 1acc1d1b3dd76e2eca76e89a52cef1f39142274c..4fd259a77eda9bd55f2369c7f949d85dfae7be47 100644
--- a/mod/box/box_lua.h
+++ b/mod/box/box_lua.h
@@ -36,7 +36,7 @@ struct lua_State;
  * (implementation of 'CALL' command code).
  */
 @interface Call: Request
-- (void) execute: (struct txn *) txn :(Port *) port;
+- (void) execute: (struct txn *) txn :(struct port *) port;
 @end
 
 /**
diff --git a/mod/box/box_lua.m b/mod/box/box_lua.m
index 89a8702e1eb6536602174c8737f600233d758c7b..893829ddc3269b13dcf7a9c59231078ae4a54e17 100644
--- a/mod/box/box_lua.m
+++ b/mod/box/box_lua.m
@@ -875,32 +875,11 @@ static const struct luaL_reg lbox_iterator_meta[] = {
  * everything into Lua stack first.
  * @sa iov_add_multret
  */
-@interface PortLua: PortNull {
-	struct lua_State *L;
-}
-+ (PortLua *) alloc;
-- (id) init: (struct lua_State *) L_arg;
-@end
-
-@implementation PortLua
-+ (PortLua *) alloc
-{
-	size_t sz = class_getInstanceSize(self);
-	id new = palloca(fiber->gc_pool, sz, sizeof(void *));
-	memset(new, 0, sz);
-	object_setClass(new, self);
-	return new;
-}
 
-- (id) init: (struct lua_State *) L_arg
-{
-	if ((self = [super init]))
-		L = L_arg;
-	return self;
-}
-
-- (void) addTuple: (struct tuple *) tuple
+static void
+port_lua_add_tuple(void *data, struct tuple *tuple)
 {
+	lua_State *L = data;
 	@try {
 		lbox_pushtuple(L, tuple);
 	} @catch (...) {
@@ -908,7 +887,12 @@ static const struct luaL_reg lbox_iterator_meta[] = {
 	}
 }
 
-@end
+struct port_vtab port_lua_vtab = {
+	port_null_add_u32,
+	port_null_dup_u32,
+	port_lua_add_tuple,
+	port_null_add_lua_multret
+};
 
 /* }}} */
 
@@ -945,7 +929,8 @@ static int lbox_process(lua_State *L)
 
 	size_t allocated_size = palloc_allocated(fiber->gc_pool);
 	struct txn *txn = txn_begin();
-	Port *port_lua = [[PortLua alloc] init: L];
+	struct port *port_lua = palloc(fiber->gc_pool, sizeof(struct port));
+	port_init(port_lua, &port_lua_vtab, L);
 	@try {
 		box_process(txn, port_lua, op, &req);
 	} @finally {
@@ -1006,7 +991,7 @@ void box_lua_find(lua_State *L, const char *name, const char *name_end)
  * Invoke a Lua stored procedure from the binary protocol
  * (implementation of 'CALL' command code).
  */
-- (void) execute: (struct txn *) txn : (Port *)port
+- (void) execute: (struct txn *) txn : (struct port *)port
 {
 	(void) txn;
 	lua_State *L = lua_newthread(root_L);
@@ -1027,7 +1012,7 @@ void box_lua_find(lua_State *L, const char *name, const char *name_end)
 		}
 		lua_call(L, nargs, LUA_MULTRET);
 		/* Send results of the called procedure to the client. */
-		[port addLuaMultret: L];
+		port_add_lua_multret(port, L);
 	} @catch (tnt_Exception *e) {
 		@throw;
 	} @catch (...) {
diff --git a/mod/box/memcached.m b/mod/box/memcached.m
index 3b3c651c7db3075ba56382c2339b735ba27ca631..a490f1d31f23cc51e4476d040aabcf60889aaf83 100644
--- a/mod/box/memcached.m
+++ b/mod/box/memcached.m
@@ -114,7 +114,7 @@ store(void *key, u32 exptime, u32 flags, u32 bytes, u8 *data)
 	 * Use a box dispatch wrapper which handles correctly
 	 * read-only/read-write modes.
 	 */
-	box_process(txn_begin(), port_null, REPLACE, req);
+	box_process(txn_begin(), &port_null, REPLACE, req);
 }
 
 static void
@@ -129,7 +129,7 @@ delete(void *key)
 	tbuf_append(req, &key_len, sizeof(key_len));
 	tbuf_append_field(req, key);
 
-	box_process(txn_begin(), port_null, DELETE, req);
+	box_process(txn_begin(), &port_null, DELETE, req);
 }
 
 static struct tuple *
@@ -277,7 +277,7 @@ void memcached_get(size_t keys_count, struct tbuf *keys,
 		stats.get_hits++;
 		stat_collect(stat_base, MEMC_GET_HIT, 1);
 
-		fiber_ref_tuple(tuple);
+		iov_ref_tuple(tuple);
 
 		if (show_cas) {
 			struct tbuf *b = tbuf_alloc(fiber->gc_pool);
diff --git a/mod/box/port.h b/mod/box/port.h
index 87491bfc1c3ff2bca7f1f247cc8aa4113452a7c8..b4184fa43cd9264f58fae747cdfb82762fce8f07 100644
--- a/mod/box/port.h
+++ b/mod/box/port.h
@@ -29,38 +29,78 @@
  * SUCH DAMAGE.
  */
 #include <util.h>
-#import "object.h"
 
 struct tuple;
 struct lua_State;
 
-@interface Port: tnt_Object
-- (void) addU32: (u32 *) u32;
-- (void) dupU32: (u32) u32;
-- (void) addTuple: (struct tuple *) tuple;
-- (void) addLuaMultret: (struct lua_State *) L;
-@end
+struct port_vtab
+{
+	u32* (*add_u32)(void *data);
+	void (*dup_u32)(void *data, u32 num);
+	void (*add_tuple)(void *data, struct tuple *tuple);
+	void (*add_lua_multret)(void *data, struct lua_State *L);
+};
 
-@interface PortNull: Port
-@end
+struct port
+{
+	struct port_vtab *vtab;
+	void *data;
+};
 
 /**
  * A hack to keep tuples alive until iov_flush(fiber->iovec).
  * Is internal to port_iproto implementation, but is also
- * used in memcached.m, which doesn't uses fiber->iovec
- * bypassing port_iproto a public declaration here.
+ * used in memcached.m, which doesn't use fiber->iovec.
  */
-void fiber_ref_tuple(struct tuple *tuple);
+void iov_ref_tuple(struct tuple *tuple);
+
+/** Create a port instance. */
+static inline void
+port_init(struct port *port, struct port_vtab *vtab, void *data)
+{
+	port->vtab = vtab;
+	port->data = data;
+}
+
+static inline u32*
+port_add_u32(struct port *port)
+{
+	return (port->vtab->add_u32)(port->data);
+}
+
+static inline void
+port_dup_u32(struct port *port, u32 num)
+{
+	(port->vtab->dup_u32)(port->data, num);
+}
+
+static inline void
+port_add_tuple(struct port *port, struct tuple *tuple)
+{
+	(port->vtab->add_tuple)(port->data, tuple);
+}
+
+static inline void
+port_add_lua_multret(struct port *port, struct lua_State *L)
+{
+	(port->vtab->add_lua_multret)(port->data, L);
+}
+/** Reused in port_lua */
+u32*
+port_null_add_u32(void *data __attribute__((unused)));
+
+void
+port_null_dup_u32(void *data __attribute__((unused)),
+		  u32 num __attribute__((unused)));
+
+void
+port_null_add_lua_multret(void *data __attribute__((unused)),
+			  struct lua_State *L __attribute__((unused)));
 
 /** These do not have state currently, thus a single
  * instance is sufficient.
  */
-Port *port_null;
-Port *port_iproto;
-
-/** Init the subsystem. */
-void port_init();
-/** Stop the susbystem. */
-void port_free();
+extern struct port port_null;
+extern struct port port_iproto;
 
 #endif /* INCLUDES_TARANTOOL_BOX_PORT_H */
diff --git a/mod/box/port.m b/mod/box/port.m
index bfd200cbad80db5a53230e827737af7fbf430c75..ee0b67d33506ded550df5c040686e9c93476426e 100644
--- a/mod/box/port.m
+++ b/mod/box/port.m
@@ -60,56 +60,58 @@ tuple_unref(void *tuple)
 }
 
 void
-fiber_ref_tuple(struct tuple *tuple)
+iov_ref_tuple(struct tuple *tuple)
 {
 	tuple_ref(tuple, 1);
 	fiber_register_cleanup(tuple_unref, tuple);
 }
 
-@implementation Port
-- (void) addU32: (u32 *) p_u32
+u32*
+port_null_add_u32(void *data __attribute__((unused)))
 {
-	[self subclassResponsibility: _cmd];
-	(void) p_u32;
+	static u32 dummy;
+	return &dummy;
 }
-- (void) dupU32: (u32) u32
+
+void
+port_null_dup_u32(void *data __attribute__((unused)),
+		  u32 num __attribute__((unused)))
 {
-	[self subclassResponsibility: _cmd];
-	(void) u32;
 }
-- (void) addTuple: (struct tuple *) tuple
+
+static void
+port_null_add_tuple(void *data __attribute__((unused)),
+		    struct tuple *tuple __attribute__((unused)))
 {
-	[self subclassResponsibility: _cmd];
-	(void) tuple;
 }
-- (void) addLuaMultret: (struct lua_State *) L
+
+void
+port_null_add_lua_multret(void *data __attribute__((unused)),
+			  struct lua_State *L __attribute__((unused)))
 {
-	[self subclassResponsibility: _cmd];
-	(void) L;
 }
-@end
-
-@interface PortIproto: Port
-@end
 
-@implementation PortIproto
-
-- (void) addU32: (u32 *) p_u32
+static u32*
+port_iproto_add_u32(void *data __attribute__((unused)))
 {
+	u32 *p_u32 = palloc(fiber->gc_pool, sizeof(u32));
 	iov_add(p_u32, sizeof(u32));
+	return p_u32;
 }
 
-- (void) dupU32: (u32) u32
+static void
+port_iproto_dup_u32(void *data __attribute__((unused)), u32 u32)
 {
 	iov_dup(&u32, sizeof(u32));
 }
 
-- (void) addTuple: (struct tuple *) tuple
+static void
+port_iproto_add_tuple(void *data __attribute__((unused)), struct tuple *tuple)
 {
 	size_t len = tuple_len(tuple);
 
 	if (len > BOX_REF_THRESHOLD) {
-		fiber_ref_tuple(tuple);
+		iov_ref_tuple(tuple);
 		iov_add(&tuple->bsize, len);
 	} else {
 		iov_dup(&tuple->bsize, len);
@@ -184,7 +186,8 @@ iov_add_lua_table(struct lua_State *L, int index)
 	}
 }
 
-void iov_add_ret(struct lua_State *L, int index)
+static void
+iov_add_ret(struct lua_State *L, int index)
 {
 	int type = lua_type(L, index);
 	struct tuple *tuple;
@@ -244,7 +247,7 @@ void iov_add_ret(struct lua_State *L, int index)
 		tnt_raise(ClientError, :ER_PROC_RET, lua_typename(L, type));
 		break;
 	}
-	fiber_ref_tuple(tuple);
+	iov_ref_tuple(tuple);
 	iov_add(&tuple->bsize, tuple_len(tuple));
 }
 
@@ -256,38 +259,36 @@ void iov_add_ret(struct lua_State *L, int index)
  * and return the number of return values first, and
  * then each return value as a tuple.
  */
-- (void) addLuaMultret: (struct lua_State *) L
+static void
+port_iproto_add_lua_multret(void *data __attribute__((unused)), struct lua_State *L)
 {
 	int nargs = lua_gettop(L);
 	iov_dup(&nargs, sizeof(u32));
 	for (int i = 1; i <= nargs; ++i)
 		iov_add_ret(L, i);
 }
-@end
 
+struct port_vtab port_null_vtab = {
+	port_null_add_u32,
+	port_null_dup_u32,
+	port_null_add_tuple,
+	port_null_add_lua_multret,
+};
 
-@implementation PortNull
-- (void) addU32: (u32 *) p_u32                  { (void) p_u32; }
-- (void) dupU32: (u32) u32	                { (void) u32; }
-- (void) addTuple: (struct tuple *) tuple       { (void) tuple; }
-- (void) addLuaMultret: (struct lua_State *) L  { (void) L; }
-@end
+struct port_vtab port_iproto_vtab = {
+	port_iproto_add_u32,
+	port_iproto_dup_u32,
+	port_iproto_add_tuple,
+	port_iproto_add_lua_multret,
+};
 
-Port *port_null;
-Port *port_iproto;
+struct port port_null = {
+	.vtab = &port_null_vtab,
+	.data = NULL,
+};
 
-void
-port_init()
-{
-	port_iproto = [[PortIproto alloc] init];
-	port_null = [[PortNull alloc] init];
-}
+struct port port_iproto = {
+	.vtab = &port_iproto_vtab,
+	.data = NULL,
+};
 
-void
-port_free()
-{
-	if (port_iproto)
-		[port_iproto free];
-	if (port_null)
-		[port_null free];
-}
diff --git a/mod/box/request.h b/mod/box/request.h
index e055aa8ec1efb5bc59b2fce7a3dfc4a730d69555..815b3b80da48e6d0ef781d0f830bb577f2647ac1 100644
--- a/mod/box/request.h
+++ b/mod/box/request.h
@@ -38,8 +38,8 @@ enum {
 	/** A limit on how many operations a single UPDATE can have. */
 	BOX_UPDATE_OP_CNT_MAX = 4000,
 };
-@class Port;
 struct txn;
+struct port;
 
 #define BOX_RETURN_TUPLE		0x01
 #define BOX_ADD				0x02
@@ -107,7 +107,7 @@ request_is_select(u32 type)
 + (Request *) alloc;
 + (Request *) build: (u32) type_arg;
 - (id) init: (struct tbuf *) data_arg;
-- (void) execute: (struct txn *) txn :(Port *) port;
+- (void) execute: (struct txn *) txn :(struct port *) port;
 @end
 
 #endif /* TARANTOOL_BOX_REQUEST_H_INCLUDED */
diff --git a/mod/box/request.m b/mod/box/request.m
index 1d11701090c40686d9975c484252c1dbf126113f..80655d6d9a4c2c7c0fb581d2b71fb167851d9674 100644
--- a/mod/box/request.m
+++ b/mod/box/request.m
@@ -66,23 +66,23 @@ read_space(struct tbuf *data)
 }
 
 static void
-port_send_tuple(u32 flags, Port *port, struct tuple *tuple)
+port_send_tuple(u32 flags, struct port *port, struct tuple *tuple)
 {
 	if (tuple) {
-		[port dupU32: 1]; /* affected tuples */
+		port_dup_u32(port, 1); /* affected tuples */
 		if (flags & BOX_RETURN_TUPLE)
-			[port addTuple: tuple];
+			port_add_tuple(port, tuple);
 	} else {
-		[port dupU32: 0]; /* affected tuples. */
+		port_dup_u32(port, 0); /* affected tuples. */
 	}
 }
 
 @interface Replace: Request
-- (void) execute: (struct txn *) txn :(Port *) port;
+- (void) execute: (struct txn *) txn :(struct port *) port;
 @end
 
 @implementation Replace
-- (void) execute: (struct txn *) txn :(Port *) port
+- (void) execute: (struct txn *) txn :(struct port *) port
 {
 	txn_add_redo(txn, type, data);
 	struct space *sp = read_space(data);
@@ -112,10 +112,10 @@ port_send_tuple(u32 flags, Port *port, struct tuple *tuple)
 
 	txn_add_undo(txn, sp, old_tuple, txn->new_tuple);
 
-	[port dupU32: 1]; /* Affected tuples */
+	port_dup_u32(port, 1); /* Affected tuples */
 
 	if (flags & BOX_RETURN_TUPLE)
-		[port addTuple: txn->new_tuple];
+		port_add_tuple(port, txn->new_tuple);
 }
 @end
 
@@ -182,7 +182,7 @@ port_send_tuple(u32 flags, Port *port, struct tuple *tuple)
  */
 
 @interface Update: Request
-- (void) execute: (struct txn *) txn :(Port *) port;
+- (void) execute: (struct txn *) txn :(struct port *) port;
 @end
 
 /** Argument of SET operation. */
@@ -709,7 +709,7 @@ update_read_ops(struct tbuf *data, u32 op_cnt)
 
 
 @implementation Update
-- (void) execute: (struct txn *) txn :(Port *) port
+- (void) execute: (struct txn *) txn :(struct port *) port
 {
 	txn_add_redo(txn, type, data);
 	struct space *sp = read_space(data);
@@ -747,11 +747,11 @@ update_read_ops(struct tbuf *data, u32 op_cnt)
 /** }}} */
 
 @interface Select: Request
-- (void) execute: (struct txn *) txn :(Port *) port;
+- (void) execute: (struct txn *) txn :(struct port *) port;
 @end
 
 @implementation Select
-- (void) execute: (struct txn *) txn :(Port *) port
+- (void) execute: (struct txn *) txn :(struct port *) port
 {
 	(void) txn; /* Not used. */
 	struct space *sp = read_space(data);
@@ -763,9 +763,8 @@ update_read_ops(struct tbuf *data, u32 op_cnt)
 	if (count == 0)
 		tnt_raise(IllegalParams, :"tuple count must be positive");
 
-	uint32_t *found = palloc(fiber->gc_pool, sizeof(*found));
+	uint32_t *found = port_add_u32(port);
 	*found = 0;
-	[port addU32: found];
 
 	ERROR_INJECT_EXCEPTION(ERRINJ_TESTING);
 
@@ -793,7 +792,7 @@ update_read_ops(struct tbuf *data, u32 op_cnt)
 				continue;
 			}
 
-			[port addTuple: tuple];
+			port_add_tuple(port, tuple);
 
 			if (limit == ++(*found))
 				break;
@@ -805,11 +804,11 @@ update_read_ops(struct tbuf *data, u32 op_cnt)
 @end
 
 @interface Delete: Request
-- (void) execute: (struct txn *) txn :(Port *) port;
+- (void) execute: (struct txn *) txn :(struct port *) port;
 @end
 
 @implementation Delete
-- (void) execute: (struct txn *) txn :(Port *) port
+- (void) execute: (struct txn *) txn :(struct port *) port
 {
 	txn_add_redo(txn, type, data);
 	u32 flags = 0;
@@ -875,7 +874,7 @@ update_read_ops(struct tbuf *data, u32 op_cnt)
 	return self;
 }
 
-- (void) execute: (struct txn *) txn :(Port *) port
+- (void) execute: (struct txn *) txn :(struct port *) port
 {
 	(void) txn;
 	(void) port;